mirror of
https://github.com/glauth/glauth.git
synced 2026-10-27 06:26:59 +00:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a92f9a7028 | |||
| 182d7c0496 | |||
| 6278bc34d4 | |||
| 78a98d75a9 | |||
| 6928b3b608 | |||
| ac16a07a63 | |||
| 363336bf94 | |||
| 07644bd957 | |||
| 558c7de15a | |||
| 9b90466286 | |||
| 3445f388b0 | |||
| 14139e878d | |||
| b6c6730cfd | |||
| b93d612e36 | |||
| 63879925c5 | |||
| 0ff1daac96 | |||
| bc4ab91888 | |||
| b8fd08b92f | |||
| c9b40b87d4 | |||
| c69168c3d1 | |||
| 98ea83c9e0 | |||
| 2243d3d049 | |||
| f793f3a08c | |||
| ba364f12c9 | |||
| 75fa168e9a | |||
| 7a573aad93 | |||
| 9f349d10c8 | |||
| 2e70a47dd6 | |||
| e546a1a289 | |||
| c550eb60e9 | |||
| 6fbd8105cd | |||
| a03a26960d | |||
| 3a13387be2 | |||
| e606ecbb56 | |||
| d9183b8f74 | |||
| 72c8e84a0d | |||
| c162ed8366 | |||
| 986a9187ee | |||
| a835e569cd | |||
| ff6cb9a9d5 | |||
| 6516741b3c | |||
| 5c1a39bd47 | |||
| a21d93bbd7 | |||
| 2020579614 | |||
| 47401aac5f | |||
| 6cd27aa790 | |||
| 9495943701 | |||
| 8de56c87a9 | |||
| 208fb1e33d | |||
| 32a13ec85a | |||
| a75743ef7d | |||
| 93a6a5542f | |||
| c431a6e426 | |||
| a9837f3112 | |||
| 2d7a2ab8ce | |||
| bc94f47c9e | |||
| 39b50ec92a | |||
| 50a78e3c56 | |||
| 5e2114e04b | |||
| 706c144f82 | |||
| 5519634a3e | |||
| b32db95f4f | |||
| 0b1c979247 | |||
| b04ff2dd67 | |||
| 94574e50a7 | |||
| 1155d214ed | |||
| 9ae4613a7f | |||
| 8d808c80a0 | |||
| 5728484e61 | |||
| 6df835dd68 | |||
| 80883e4a57 | |||
| 02970c82ff | |||
| 7124c108a7 | |||
| 136d7d7833 | |||
| f0cf6488e4 | |||
| dc90cd6819 | |||
| c2aca63246 | |||
| 60ac4368c6 | |||
| 64fbf20dcd | |||
| de571e8cb6 | |||
| c232ee0476 | |||
| 4f82bf2baf | |||
| 7608bc0133 | |||
| 2ae84a9034 | |||
| ac85255d86 | |||
| 2a16b13a01 | |||
| 5b84deb1c1 | |||
| c5cc0f5572 | |||
| dd71d9ac4e | |||
| 5f5c12e78d | |||
| d5a1b6f216 | |||
| 82bff3fc36 | |||
| fd9aaa243d | |||
| e2c9210beb | |||
| e03e9216fa | |||
| 70ff3600ec | |||
| 68edcdc228 | |||
| f076f868dc | |||
| f039bbb964 |
+3
-1
@@ -10,9 +10,11 @@ certs/
|
||||
# Built binaries
|
||||
bin
|
||||
|
||||
bindata.go
|
||||
pkg/assets/bindata.go
|
||||
|
||||
.*.swp
|
||||
|
||||
# Ignoring travis CLI tool logs
|
||||
*travis.log
|
||||
|
||||
certs/
|
||||
+7
-8
@@ -2,10 +2,11 @@ language: go
|
||||
|
||||
# USE THIS alongside makefile tag detection: $TRAVIS_TAG
|
||||
|
||||
# if you change the version here, also update the travis deploy condition
|
||||
go:
|
||||
- 1.9.x # older version
|
||||
- 1.10.x # last version
|
||||
- 1.11.x # current version
|
||||
- 1.11.x # older version
|
||||
- 1.12.x # last version
|
||||
- 1.13.x # current version
|
||||
|
||||
# Speed up git clones
|
||||
git:
|
||||
@@ -65,16 +66,14 @@ script:
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: CsQb802CJJlum5suwo3Yvw1TkmX56O6TPrtJqMI0Qm6A878dhrZIjyVmfG3Iq9rtZvfFhK+Y3SIfPZp8BjV3cicc+X1Yh1VvMG12ukqHSruvGvLG49dtnq40P31u85Y4HdBk+W8YIsCQHEonsID9l+q2LbhuiC358UF+HNgzESEz6ZyTZz0AvAJzm1uyiUiPTHMFwqN7qQsookmLXf490rqp9YmFFqte+o3/JwoYXc3pE/rBMJitPSJNGULzu2QofV5gJ0JxgPWlfF5DsHHEi1jhpBcTfiFzEvkdcl1SVme9MMNPue7j8u9+PLJ6uIdlnTeBcMPKgtzOg/kBqY4+3QzY4aPXjKm3+MF3Cau66+g3QC7dKenvYefapQYN7t1SSpiR9X3TLWZrBbw2ovMe3em+uMnIM08bemzj8DapRgKXmYKWnwTlA9KLf0Vl22255OzMGarkhx94cDvUHmEkNo7qBXqmUxPo0Fc5XJyD7JmUNgYoWj0/NjyN09VG22FL2FW44HR3eitq2wSFSMO/jsC2rLuCnyLHViP+O2YJu0T4Q3gdQUcdQmjT7y8ogH5Shn9t2wY4NRxI3MyXO7+fGEOj1DaD6CFQwcSwbJVpIzS6J8z7+rRrzE9QkXeCmwNNt5UkpR9lYmOEzpc00rTRimwjfmJn/nTXsIDiQg9iZJM=
|
||||
secure: lPI5FftcQB1eHihN9daeFOT97vZcfq2w0hRb6amC6U4P+K7Ih23R9NmWhDuvGM9CPLaAqgOKiHWIUnP4q9EeBeAUMgg6FI2syr/0nDYz5cHqZMMPWFgeDvn+HqfMdFIVeiKT80+uGY/JNsMI8lsJ0eILEp0GpyIZkoAewCEyxJ/fcLM5htGnQIQ8mQ+2ZXflvPofv6/RPHgu3ryC0icJFVMREIivp2QIZBpFnawnI+OE8ZzxD5R05gfg3IJtYtc/4diX4RPyqVTCqljU/x7/g9KgNsnaLGEz37pBUS0IQcq4ITX/cm4y5LLWPV6JugxjyTq1SXKJnHEgfEFk5/hPM7Q1DMZJ5tGpb2B4aSMwB6YHwvVW83ih96Vw8ZgIrxWT9LyF4n9AHakLhDIiJieDXa3dvG7U5oTxlSP/RJkXlMZEJ3lS/1RZ9vvQ0EOEMAO6ePQzSZ4EM/UShFalBI+fqDtzqxFGeeCWw6lwiKLF2GF7enT8+wLsWOMI43JM5uPAq2g0KFMtJEEcK08IFraObvK5bcFH7snCDtsAaLyjdkVIIBteNtfDsXQ8PMi4bQpLr5kpY68dkyuxDr4IpL1GKqlpe2LbjkzsGrq5wifcHMFmUkEfY/6/ELFc9fL+1ZnMAe7YiW41AvX+hrgVmc3M8zp1kVvy2oERV4LfV1vfaEU=
|
||||
file_glob: true
|
||||
file: bin/*
|
||||
draft: true
|
||||
name: "DRAFT RELEASE: $TRAVIS_TAG"
|
||||
body: "Draft release, committed with message: $TRAVIS_COMMIT_MESSAGE"
|
||||
body: "# Commit Details<br />$TRAVIS_COMMIT_MESSAGE<br /><br /># Note About Non-Linux 64bit Files<br /> While I expect the code is system-independent, there is not a good (and free) CI system which can be easily used to continuously test releases on ARM, BSD, Linux-32bit, and Windows.<br /><br />As such, the primary release platform is Linux 64 bit. The extent of testing on these packages consists solely of cross-compiling for these architectures from a linux 64 bit system.<br /><br />We will accept PRs which fix bugs on these platforms, but be aware these binaries will not be tested regularly, and instead are provided for the convenience of those who feel comfortable using them with this knowledge."
|
||||
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
condition: $TRAVIS_GO_VERSION = "1.10.1"
|
||||
condition: $TRAVIS_GO_VERSION =~ ^1\.13
|
||||
repo: glauth/glauth
|
||||
|
||||
+5
-5
@@ -15,6 +15,7 @@ WORKDIR /app
|
||||
ENV GOPATH=/tmp/gocode
|
||||
ENV GOOS=linux
|
||||
ENV GOARCH=amd64
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
# Only needed for alpine builds
|
||||
RUN apk add --no-cache git bzr make
|
||||
@@ -23,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
|
||||
@@ -32,7 +33,7 @@ RUN make linux64 && cp ./bin/glauth64 /app/glauth
|
||||
# Run Step
|
||||
#################
|
||||
|
||||
FROM golang:alpine as run
|
||||
FROM alpine as run
|
||||
MAINTAINER Ben Yanke <ben@benyanke.com>
|
||||
|
||||
# Copies a sample config to be used if a volume isn't mounted with user's config
|
||||
@@ -46,14 +47,13 @@ COPY --from=build /app/scripts/docker/start.sh /app/docker/
|
||||
COPY --from=build /app/scripts/docker/default-config.cfg /app/docker/
|
||||
|
||||
# Install ldapsearch for container health checks, then ensure ldapsearch is installed
|
||||
RUN apk update && apk add --no-cache openldap-clients && which ldapsearch && rm -rf /var/cache/apk/*
|
||||
RUN apk update && apk add --no-cache dumb-init openldap-clients && which ldapsearch && rm -rf /var/cache/apk/*
|
||||
|
||||
# Install init
|
||||
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64 && chmod +x /usr/local/bin/dumb-init
|
||||
|
||||
# Expose web and LDAP ports
|
||||
EXPOSE 389 636 5555
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||
CMD ["/bin/sh", "/app/docker/start.sh"]
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ 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
|
||||
BUILD_FILES=glauth.go
|
||||
TRIM_FLAGS=-gcflags "all=-trimpath=${PWD}" -asmflags "all=-trimpath=${PWD}"
|
||||
|
||||
#####################
|
||||
# High level commands
|
||||
@@ -55,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
|
||||
@@ -69,25 +70,25 @@ devrun:
|
||||
|
||||
|
||||
linux32:
|
||||
GOOS=linux GOARCH=386 go build -ldflags "${BUILD_VARS}" -o bin/glauth32 ${BUILD_FILES} && cd bin && sha256sum glauth32 > glauth32.sha256
|
||||
GOOS=linux GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth32 ${BUILD_FILES} && cd bin && sha256sum glauth32 > glauth32.sha256
|
||||
|
||||
linux64:
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "${BUILD_VARS}" -o bin/glauth64 ${BUILD_FILES} && cd bin && sha256sum glauth64 > glauth64.sha256
|
||||
GOOS=linux GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth64 ${BUILD_FILES} && cd bin && sha256sum glauth64 > glauth64.sha256
|
||||
|
||||
linuxarm32:
|
||||
GOOS=linux GOARCH=arm go build -ldflags "${BUILD_VARS}" -o bin/glauth-arm32 ${BUILD_FILES} && cd bin && sha256sum glauth-arm32 > glauth-arm32.sha256
|
||||
GOOS=linux GOARCH=arm go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-arm32 ${BUILD_FILES} && cd bin && sha256sum glauth-arm32 > glauth-arm32.sha256
|
||||
|
||||
linuxarm64:
|
||||
GOOS=linux GOARCH=arm64 go build -ldflags "${BUILD_VARS}" -o bin/glauth-arm64 ${BUILD_FILES} && cd bin && sha256sum glauth-arm64 > glauth-arm64.sha256
|
||||
GOOS=linux GOARCH=arm64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-arm64 ${BUILD_FILES} && cd bin && sha256sum glauth-arm64 > glauth-arm64.sha256
|
||||
|
||||
darwin64:
|
||||
GOOS=darwin GOARCH=amd64 go build -ldflags "${BUILD_VARS}" -o bin/glauthOSX ${BUILD_FILES} && cd bin && sha256sum glauthOSX > glauthOSX.sha256
|
||||
GOOS=darwin GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauthOSX ${BUILD_FILES} && cd bin && sha256sum glauthOSX > glauthOSX.sha256
|
||||
|
||||
win32:
|
||||
GOOS=windows GOARCH=386 go build -ldflags "${BUILD_VARS}" -o bin/glauth-win32 ${BUILD_FILES} && cd bin && sha256sum glauth-win32 > glauth-win32.sha256
|
||||
GOOS=windows GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win32 ${BUILD_FILES} && cd bin && sha256sum glauth-win32 > glauth-win32.sha256
|
||||
|
||||
win64:
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags "${BUILD_VARS}" -o bin/glauth-win64 ${BUILD_FILES} && cd bin && sha256sum glauth-win64 > glauth-win64.sha256
|
||||
GOOS=windows GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win64 ${BUILD_FILES} && cd bin && sha256sum glauth-win64 > glauth-win64.sha256
|
||||
|
||||
|
||||
verify:
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/docopt/docopt-go"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
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"
|
||||
logging "github.com/op/go-logging"
|
||||
"gopkg.in/amz.v1/aws"
|
||||
"gopkg.in/amz.v1/s3"
|
||||
)
|
||||
@@ -28,7 +32,7 @@ const programName = "glauth"
|
||||
var usage = `glauth: securely expose your LDAP for external auth
|
||||
|
||||
Usage:
|
||||
glauth [options] -c <file|s3url>
|
||||
glauth [options] -c <file|s3 url>
|
||||
glauth -h --help
|
||||
glauth --version
|
||||
|
||||
@@ -41,91 +45,14 @@ Options:
|
||||
--version Show version.
|
||||
`
|
||||
|
||||
// exposed expvar variables
|
||||
var stats_frontend = expvar.NewMap("proxy_frontend")
|
||||
var stats_backend = expvar.NewMap("proxy_backend")
|
||||
var stats_general = expvar.NewMap("proxy")
|
||||
var (
|
||||
log *logging.Logger
|
||||
args map[string]interface{}
|
||||
stderr *logging.LogBackend
|
||||
yubiAuth *yubigo.YubiAuth
|
||||
|
||||
// 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 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
|
||||
YubikeyClientID string
|
||||
YubikeySecret string
|
||||
Frontend configFrontend
|
||||
LDAP configLDAP
|
||||
LDAPS configLDAPS
|
||||
Groups []configGroup
|
||||
Syslog bool
|
||||
Users []configUser
|
||||
ConfigFile string
|
||||
AwsAccessKeyId string
|
||||
AwsSecretAccessKey string
|
||||
AwsRegion string
|
||||
}
|
||||
|
||||
var log = logging.MustGetLogger(programName)
|
||||
activeConfig = &config.Config{}
|
||||
)
|
||||
|
||||
// Reads builtime vars and returns a full string containing info about
|
||||
// the currently running version of the software. Primarily used by the
|
||||
@@ -170,101 +97,101 @@ func getVersionString() string {
|
||||
}
|
||||
|
||||
func main() {
|
||||
stderr := initLogging()
|
||||
stderr = initLogging()
|
||||
log.Debug("AP start")
|
||||
|
||||
cfg, err := doConfig()
|
||||
if err != nil {
|
||||
if err := parseArgs(); err != nil {
|
||||
log.Fatalf("Could not parse command-line arguments", err.Error())
|
||||
}
|
||||
if err := doConfig(); err != nil {
|
||||
log.Fatalf("Configuration file error: %s", err.Error())
|
||||
}
|
||||
if cfg.Syslog {
|
||||
enableSyslog(stderr)
|
||||
}
|
||||
|
||||
startService()
|
||||
}
|
||||
|
||||
func startService() {
|
||||
// stats
|
||||
stats_general.Set("version", stringer(LastGitTag))
|
||||
stats.General.Set("version", stats.Stringer(LastGitTag))
|
||||
|
||||
// web API
|
||||
if cfg.API.Enabled {
|
||||
if activeConfig.API.Enabled {
|
||||
log.Debug("Web API enabled")
|
||||
go RunAPI(cfg)
|
||||
go frontend.RunAPI(log, activeConfig)
|
||||
}
|
||||
|
||||
yubiAuth := (*yubigo.YubiAuth)(nil)
|
||||
startConfigWatcher()
|
||||
|
||||
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
|
||||
yubiAuth, err = yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Yubikey Auth failed")
|
||||
}
|
||||
}
|
||||
|
||||
// configure the backend
|
||||
s := ldap.NewServer()
|
||||
s.EnforceLDAP = true
|
||||
var handler Backend
|
||||
switch cfg.Backend.Datastore {
|
||||
case "ldap":
|
||||
handler = newLdapHandler(cfg)
|
||||
case "config":
|
||||
handler = newConfigHandler(cfg, yubiAuth)
|
||||
default:
|
||||
log.Fatalf("Unsupported backend %s - must be 'config' or 'ldap'.", cfg.Backend.Datastore)
|
||||
}
|
||||
log.Notice(fmt.Sprintf("Using %s backend", cfg.Backend.Datastore))
|
||||
s.BindFunc("", handler)
|
||||
s.SearchFunc("", handler)
|
||||
s.CloseFunc("", handler)
|
||||
|
||||
if cfg.LDAP.Enabled {
|
||||
// Dont block if also starting a LDAPS server afterwards
|
||||
shouldBlock := !cfg.LDAPS.Enabled
|
||||
|
||||
if shouldBlock {
|
||||
startLDAP(&cfg.LDAP, s)
|
||||
} else {
|
||||
go startLDAP(&cfg.LDAP, s)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Enabled {
|
||||
// Always block here
|
||||
startLDAPS(&cfg.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")
|
||||
}
|
||||
|
||||
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 startConfigWatcher() {
|
||||
configFileLocation := getConfigLocation()
|
||||
|
||||
if strings.HasPrefix(configFileLocation, "s3://") {
|
||||
return
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start config-watcher: %s", err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
if activeConfig.WatchConfig {
|
||||
if event.Op.String() == "WRITE" {
|
||||
if err := doConfig(); err != nil {
|
||||
log.Warningf("Could not reload config: %sHolding on to old config", err.Error())
|
||||
} else {
|
||||
log.Notice("Config was reloaded")
|
||||
}
|
||||
}
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
if activeConfig.WatchConfig {
|
||||
log.Info("Error!", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
watcher.Add(configFileLocation)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if args, err = docopt.Parse(usage, nil, true, getVersionString(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// doConfig reads the cli flags and config file
|
||||
func doConfig() (*config, error) {
|
||||
cfg := config{}
|
||||
func getConfigLocation() string {
|
||||
return args["--config"].(string)
|
||||
}
|
||||
|
||||
func parseConfigFile(configFileLocation string) (*config.Config, error) {
|
||||
cfg := config.Config{}
|
||||
// setup defaults
|
||||
cfg.LDAP.Enabled = false
|
||||
cfg.LDAPS.Enabled = true
|
||||
|
||||
// parse the command-line args
|
||||
args, err := docopt.Parse(usage, nil, true, getVersionString(), false)
|
||||
if err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
cfg.Backend.NameFormat = "cn"
|
||||
cfg.Backend.GroupFormat = "ou"
|
||||
cfg.Backend.SSHKeyAttr = "sshPublicKey"
|
||||
|
||||
// parse the config file
|
||||
if strings.HasPrefix(args["--config"].(string), "s3://") {
|
||||
if strings.HasPrefix(configFileLocation, "s3://") {
|
||||
if _, present := aws.Regions[args["-r"].(string)]; present == false {
|
||||
return &cfg, fmt.Errorf("Invalid AWS region: %s", args["-r"])
|
||||
}
|
||||
@@ -280,7 +207,7 @@ func doConfig() (*config, error) {
|
||||
}
|
||||
}
|
||||
// parse S3 url
|
||||
s3url := strings.TrimPrefix(args["--config"].(string), "s3://")
|
||||
s3url := strings.TrimPrefix(configFileLocation, "s3://")
|
||||
parts := strings.SplitN(s3url, "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return &cfg, fmt.Errorf("Invalid S3 URL: %s", s3url)
|
||||
@@ -294,16 +221,15 @@ func doConfig() (*config, error) {
|
||||
return &cfg, err
|
||||
}
|
||||
} else { // local config file
|
||||
if _, err := toml.DecodeFile(args["--config"].(string), &cfg); err != nil {
|
||||
if _, err := toml.DecodeFile(configFileLocation, &cfg); err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
}
|
||||
// enable features
|
||||
if cfg.Debug {
|
||||
logging.SetLevel(logging.DEBUG, programName)
|
||||
log.Debug("Debugging enabled")
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
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])")
|
||||
@@ -357,16 +283,67 @@ func doConfig() (*config, error) {
|
||||
cfg.Backend.Datastore = "config"
|
||||
case "config":
|
||||
case "ldap":
|
||||
case "owncloud":
|
||||
default:
|
||||
return &cfg, fmt.Errorf("Invalid backend %s - must be 'config' or 'ldap'.", cfg.Backend.Datastore)
|
||||
return &cfg, fmt.Errorf("invalid backend %s - must be 'config', 'ldap' or 'owncloud", cfg.Backend.Datastore)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// doConfig reads the cli flags and config file
|
||||
func doConfig() error {
|
||||
// Parse config-file into config{} struct
|
||||
cfg, err := parseConfigFile(getConfigLocation())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle validation and parsing of old [frontend] section into [ldap] and/or [ldaps] sections
|
||||
cfg, err = handleConfig(*cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Before greenlighting new config entirely, lets make sure the yubiauth works - in case they changed
|
||||
if activeConfig.YubikeyClientID != cfg.YubikeyClientID || activeConfig.YubikeySecret != cfg.YubikeySecret {
|
||||
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
|
||||
_yubiAuth, err := yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// No errors, override
|
||||
yubiAuth = _yubiAuth
|
||||
}
|
||||
}
|
||||
|
||||
// All config is validated and alright, copy to ativeConfig
|
||||
if err := copier.Copy(activeConfig, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle logging settings for new config
|
||||
// - we do this last to make sure we only respect a fully validated config
|
||||
stderr = initLogging()
|
||||
|
||||
if activeConfig.Debug {
|
||||
logging.SetLevel(logging.DEBUG, programName)
|
||||
log.Debug("Debugging enabled")
|
||||
}
|
||||
if activeConfig.Syslog {
|
||||
enableSyslog(stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initLogging sets up logging to stderr
|
||||
func initLogging() *logging.LogBackend {
|
||||
log = logging.MustGetLogger(programName)
|
||||
|
||||
format := "%{color}%{time:15:04:05.000000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
|
||||
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
|
||||
logging.SetBackend(logBackend)
|
||||
logging.SetLevel(logging.NOTICE, programName)
|
||||
logging.SetFormatter(logging.MustStringFormatter(format))
|
||||
@@ -381,6 +358,8 @@ func enableSyslog(stderrBackend *logging.LogBackend) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
logging.SetBackend(stderrBackend, syslogBackend)
|
||||
|
||||
log.Debug("Syslog enabled")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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/jteeuwen/go-bindata v3.0.7+incompatible // indirect
|
||||
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,46 @@
|
||||
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/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts=
|
||||
github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/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,24 +1,30 @@
|
||||
package main
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"net"
|
||||
"sort"
|
||||
"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
|
||||
@@ -29,30 +35,31 @@ 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), ",")
|
||||
groupName := ""
|
||||
userName := ""
|
||||
if len(parts) == 1 {
|
||||
userName = strings.TrimPrefix(parts[0], "cn=")
|
||||
userName = strings.TrimPrefix(parts[0], h.cfg.Backend.NameFormat+"=")
|
||||
} else if len(parts) == 2 {
|
||||
userName = strings.TrimPrefix(parts[0], "cn=")
|
||||
groupName = strings.TrimPrefix(parts[1], "ou=")
|
||||
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 {
|
||||
@@ -61,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 {
|
||||
@@ -74,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
|
||||
}
|
||||
|
||||
@@ -127,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
|
||||
}
|
||||
|
||||
@@ -142,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
|
||||
}
|
||||
|
||||
@@ -162,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 {
|
||||
@@ -187,75 +194,85 @@ func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn
|
||||
case "posixgroup":
|
||||
for _, g := range h.cfg.Groups {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{g.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"description", []string{fmt.Sprintf("%s via LDAP", g.Name)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", g.UnixID)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixGroup"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"uniqueMember", h.getGroupMembers(g.UnixID)})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"memberUid", h.getGroupMemberIDs(g.UnixID)})
|
||||
dn := fmt.Sprintf("cn=%s,ou=groups,%s", g.Name, h.cfg.Backend.BaseDN)
|
||||
entries = append(entries, &ldap.Entry{dn, attrs})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{g.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{g.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s", g.Name)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", g.UnixID)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uniqueMember", Values: h.getGroupMembers(g.UnixID)})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: h.getGroupMemberIDs(g.UnixID)})
|
||||
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.cfg.Backend.GroupFormat, h.cfg.Backend.BaseDN)
|
||||
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
|
||||
}
|
||||
case "posixaccount", "":
|
||||
for _, u := range h.cfg.Users {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{u.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"uid", []string{u.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{u.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{u.Name}})
|
||||
|
||||
if len(u.GivenName) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"givenName", []string{u.GivenName}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{u.GivenName}})
|
||||
}
|
||||
|
||||
if len(u.SN) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"sn", []string{u.SN}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "sn", Values: []string{u.SN}})
|
||||
}
|
||||
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"ou", []string{h.getGroupName(u.PrimaryGroup)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"uidNumber", []string{fmt.Sprintf("%d", u.UnixID)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{h.getGroupName(u.PrimaryGroup)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uidNumber", Values: []string{fmt.Sprintf("%d", u.UnixID)}})
|
||||
|
||||
if u.Disabled {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"inactive"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"active"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
|
||||
}
|
||||
|
||||
if len(u.Mail) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"mail", []string{u.Mail}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "mail", Values: []string{u.Mail}})
|
||||
}
|
||||
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixAccount"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixAccount", "shadowAccount"}})
|
||||
|
||||
if len(u.LoginShell) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{u.LoginShell}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{u.LoginShell}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{"/bin/bash"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{"/bin/bash"}})
|
||||
}
|
||||
|
||||
if len(u.Homedir) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{u.Homedir}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{u.Homedir}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{"/home/" + u.Name}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{"/home/" + u.Name}})
|
||||
}
|
||||
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"description", []string{fmt.Sprintf("%s via LDAP", u.Name)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"gecos", []string{fmt.Sprintf("%s via LDAP", u.Name)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", u.PrimaryGroup)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"memberOf", h.getGroupDNs(append(u.OtherGroups, u.PrimaryGroup))})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s", u.Name)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "gecos", Values: []string{fmt.Sprintf("%s", u.Name)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", u.PrimaryGroup)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: h.getGroupDNs(append(u.OtherGroups, u.PrimaryGroup))})
|
||||
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowExpire", Values: []string{"-1"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowFlag", Values: []string{"134538308"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowInactive", Values: []string{"-1"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowLastChange", Values: []string{"11000"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowMax", Values: []string{"99999"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowMin", Values: []string{"-1"}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "shadowWarning", Values: []string{"7"}})
|
||||
|
||||
if len(u.SSHKeys) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"sshPublicKey", u.SSHKeys})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: h.cfg.Backend.SSHKeyAttr, Values: u.SSHKeys})
|
||||
}
|
||||
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.cfg.Backend.BaseDN)
|
||||
entries = append(entries, &ldap.Entry{dn, attrs})
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.cfg.Backend.NameFormat, u.Name, h.cfg.Backend.GroupFormat, h.getGroupName(u.PrimaryGroup), 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))
|
||||
return ldap.ServerSearchResult{entries, []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, nil
|
||||
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
|
||||
}
|
||||
|
||||
@@ -264,12 +281,12 @@ func (h configHandler) getGroupMembers(gid int) []string {
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.PrimaryGroup == gid {
|
||||
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.cfg.Backend.BaseDN)
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.cfg.Backend.NameFormat, u.Name, h.cfg.Backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.cfg.Backend.BaseDN)
|
||||
members[dn] = true
|
||||
} else {
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.cfg.Backend.BaseDN)
|
||||
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.cfg.Backend.NameFormat, u.Name, h.cfg.Backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.cfg.Backend.BaseDN)
|
||||
members[dn] = true
|
||||
}
|
||||
}
|
||||
@@ -319,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)
|
||||
|
||||
@@ -347,7 +364,7 @@ func (h configHandler) getGroupDNs(gids []int) []string {
|
||||
for _, gid := range gids {
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.UnixID == gid {
|
||||
dn := fmt.Sprintf("cn=%s,ou=groups,%s", g.Name, h.cfg.Backend.BaseDN)
|
||||
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.cfg.Backend.GroupFormat, h.cfg.Backend.BaseDN)
|
||||
groups[dn] = true
|
||||
}
|
||||
|
||||
@@ -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,28 +1,36 @@
|
||||
package main
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kr/pretty"
|
||||
"github.com/nmcclain/ldap"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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
|
||||
log *logging.Logger
|
||||
cfg *config.Config
|
||||
lock *sync.Mutex // for sessions and servers
|
||||
sessions map[string]ldapSession
|
||||
servers []ldapBackend
|
||||
lock sync.Mutex // for sessions and servers
|
||||
doPing chan bool
|
||||
cfg *config
|
||||
}
|
||||
|
||||
// global lock for ldapHandler sessions & servers manipulation
|
||||
var ldaplock sync.Mutex
|
||||
|
||||
type ldapSession struct {
|
||||
id string
|
||||
c net.Conn
|
||||
@@ -43,11 +51,13 @@ 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,
|
||||
}
|
||||
// parse LDAP URLs
|
||||
for _, ldapurl := range cfg.Backend.Servers {
|
||||
@@ -66,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(
|
||||
@@ -105,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 {
|
||||
@@ -130,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
|
||||
}
|
||||
|
||||
@@ -139,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,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 {
|
||||
@@ -229,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")
|
||||
}
|
||||
@@ -258,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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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 {
|
||||
log *logging.Logger
|
||||
cfg *config.Config
|
||||
meUrl string
|
||||
sessions map[string]ownCloudSession
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
|
||||
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
|
||||
|
||||
stats.Frontend.Add("bind_reqs", 1)
|
||||
|
||||
// parse the bindDN - ensure that the bindDN ends with the BaseDN
|
||||
if !strings.HasSuffix(bindDN, baseDN) {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
parts := strings.Split(strings.TrimSuffix(bindDN, baseDN), ",")
|
||||
if len(parts) > 2 {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
userName := strings.TrimPrefix(parts[0], "cn=")
|
||||
|
||||
// try to login
|
||||
if !h.login(userName, bindSimplePw) {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: User %s login failed", userName))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
id := connID(conn)
|
||||
h.lock.Lock()
|
||||
h.sessions[id] = ownCloudSession{
|
||||
log: h.log,
|
||||
user: userName,
|
||||
password: bindSimplePw,
|
||||
baseUrl: h.cfg.Backend.Servers[0],
|
||||
useGraphAPI: h.cfg.Backend.UseGraphAPI,
|
||||
}
|
||||
h.lock.Unlock()
|
||||
|
||||
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 ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s 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, h.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{}
|
||||
filterEntity, err := ldap.GetFilterObjectClass(searchReq.Filter)
|
||||
if err != nil {
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("search error: error parsing filter: %s", searchReq.Filter)
|
||||
}
|
||||
h.lock.Lock()
|
||||
id := connID(conn)
|
||||
session := h.sessions[id]
|
||||
h.lock.Unlock()
|
||||
|
||||
switch filterEntity {
|
||||
default:
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("search error: unhandled filter type: %s [%s]", filterEntity, searchReq.Filter)
|
||||
case "posixgroup":
|
||||
groups, err := session.getGroups()
|
||||
if err != nil {
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("search error: error getting groups")
|
||||
}
|
||||
for _, g := range groups {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{*g.ID}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s from ownCloud", *g.ID)}})
|
||||
// attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{fmt.Sprintf("%d", g.UnixID)}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup"}})
|
||||
if g.Members != nil {
|
||||
members := make([]string, len(g.Members))
|
||||
for i, v := range g.Members {
|
||||
members[i] = *v.ID
|
||||
}
|
||||
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: members})
|
||||
}
|
||||
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(h.cfg.Backend.BaseDN) {
|
||||
parts := strings.Split(strings.TrimSuffix(searchBaseDN, baseDN), ",")
|
||||
if len(parts) == 1 {
|
||||
userName = strings.TrimPrefix(parts[0], "cn=")
|
||||
}
|
||||
}
|
||||
users, err := session.getUsers(userName)
|
||||
if err != nil {
|
||||
h.log.Debug(err)
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("search error: error getting users")
|
||||
}
|
||||
for _, u := range users {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{*u.ID}})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{*u.ID}})
|
||||
if u.DisplayName != nil {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "givenName", Values: []string{*u.DisplayName}})
|
||||
}
|
||||
if u.Mail != nil {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "mail", Values: []string{*u.Mail}})
|
||||
}
|
||||
|
||||
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", 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)
|
||||
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 ownCloudHandler) Close(boundDN string, conn net.Conn) error {
|
||||
conn.Close() // close connection to the server when then client is closed
|
||||
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 (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 {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
type OCSGroupsResponse struct {
|
||||
Ocs struct {
|
||||
Meta struct {
|
||||
Message interface{} `json:"message"`
|
||||
Statuscode int `json:"statuscode"`
|
||||
Status string `json:"status"`
|
||||
} `json:"meta"`
|
||||
Data struct {
|
||||
Groups []string `json:"groups"`
|
||||
} `json:"data"`
|
||||
} `json:"ocs"`
|
||||
}
|
||||
|
||||
func (s ownCloudSession) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.SetBasicAuth(s.user, s.password)
|
||||
return http.DefaultTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (s ownCloudSession) getGroups() ([]msgraph.Group, error) {
|
||||
if s.useGraphAPI {
|
||||
ctx := context.Background()
|
||||
req := s.NewClient().Groups().Request()
|
||||
req.Expand("members")
|
||||
return req.Get(ctx)
|
||||
}
|
||||
groupsUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/groups?format=json", s.baseUrl)
|
||||
|
||||
req, _ := http.NewRequest("GET", groupsUrl, nil)
|
||||
req.SetBasicAuth(s.user, s.password)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var f OCSGroupsResponse
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]msgraph.Group, len(f.Ocs.Data.Groups))
|
||||
for i, v := range f.Ocs.Data.Groups {
|
||||
ret[i] = msgraph.Group{DirectoryObject: msgraph.DirectoryObject{Entity: msgraph.Entity{ID: &v}}}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type OCSUsersResponse struct {
|
||||
Ocs struct {
|
||||
Data struct {
|
||||
Users []string `json:"users"`
|
||||
} `json:"data"`
|
||||
Meta struct {
|
||||
Statuscode int `json:"statuscode"`
|
||||
Message interface{} `json:"message"`
|
||||
Status string `json:"status"`
|
||||
} `json:"meta"`
|
||||
} `json:"ocs"`
|
||||
}
|
||||
|
||||
// NewClient returns GraphService request builder with default base URL
|
||||
func (s ownCloudSession) NewClient() *msgraph.GraphServiceRequestBuilder {
|
||||
graphAPIBaseUrl := fmt.Sprintf("%s/index.php/apps/graphapi/v1.0/", s.baseUrl)
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: s,
|
||||
}
|
||||
g := msgraph.NewClient(httpClient)
|
||||
g.SetURL(graphAPIBaseUrl)
|
||||
return g
|
||||
}
|
||||
|
||||
func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
|
||||
if s.useGraphAPI {
|
||||
ctx := context.Background()
|
||||
req := s.NewClient().Users()
|
||||
if len(userName) > 0 {
|
||||
u, err := req.ID(userName).Request().Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []msgraph.User{*u}, nil
|
||||
}
|
||||
return req.Request().Get(ctx)
|
||||
}
|
||||
usersUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/users?format=json", s.baseUrl)
|
||||
|
||||
req, _ := http.NewRequest("GET", usersUrl, nil)
|
||||
req.SetBasicAuth(s.user, s.password)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var f OCSUsersResponse
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]msgraph.User, len(f.Ocs.Data.Users))
|
||||
for i, v := range f.Ocs.Data.Users {
|
||||
ret[i] = msgraph.User{DirectoryObject: msgraph.DirectoryObject{Entity: msgraph.Entity{ID: &v}}}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
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(log *logging.Logger, cfg *config.Config) Handler {
|
||||
meUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/user?format=json", cfg.Backend.Servers[0])
|
||||
|
||||
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
|
||||
+17
-5
@@ -4,7 +4,11 @@
|
||||
#################
|
||||
# General configuration.
|
||||
debug = true
|
||||
#syslog = true
|
||||
# syslog = true
|
||||
#
|
||||
# Enable hot-reload of configuration on changes
|
||||
# - does NOT work [ldap], [ldaps], [backend] or [api] sections
|
||||
# watchconfig = true
|
||||
|
||||
#################
|
||||
# yubikeyclientid = "yubi-api-clientid"
|
||||
@@ -25,11 +29,11 @@ debug = true
|
||||
# This ([ldap] and [ldaps]) is the new server-config format
|
||||
[ldap]
|
||||
enabled = true
|
||||
listen = "localhost:3893"
|
||||
listen = "0.0.0.0:3893"
|
||||
|
||||
[ldaps]
|
||||
enabled = true
|
||||
listen = "localhost:3894"
|
||||
listen = "0.0.0.0:3894"
|
||||
cert = "certs/server.crt"
|
||||
key = "certs/server.key"
|
||||
|
||||
@@ -38,6 +42,15 @@ debug = true
|
||||
[backend]
|
||||
datastore = "config"
|
||||
baseDN = "dc=glauth,dc=com"
|
||||
|
||||
## Configure dn format to use structures like
|
||||
## "uid=serviceuser,cn=svcaccts,$BASEDN" instead of "cn=serviceuser,ou=svcaccts,$BASEDN"
|
||||
## to help ease migrations from other LDAP systems
|
||||
# nameformat = "uid"
|
||||
# groupformat = "cn"
|
||||
|
||||
## Configure ssh-key attribute name, default is 'sshPublicKey'
|
||||
# sshkeyattr = "ipaSshPubKey"
|
||||
|
||||
#################
|
||||
# The users section contains a hardcoded list of valid users.
|
||||
@@ -59,7 +72,6 @@ debug = true
|
||||
loginShell = "/bin/sh"
|
||||
homeDir = "/root"
|
||||
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
|
||||
passappsha256 = ["6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a"]
|
||||
sshkeys = ["ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwVw3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4hC/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLVIfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XBYnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+txSuhw== rsa-key-20160209"]
|
||||
passappsha256 = [
|
||||
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
|
||||
@@ -102,6 +114,6 @@ debug = true
|
||||
[api]
|
||||
enabled = true
|
||||
tls = false # enable TLS for production!!
|
||||
listen = "localhost:5555"
|
||||
listen = "0.0.0.0:5555"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
|
||||
@@ -24,5 +24,5 @@ echo "";
|
||||
/app/glauth -c /app/config/config.cfg
|
||||
|
||||
echo ""
|
||||
echo "GLauth has exited.""
|
||||
echo "GLauth has exited."
|
||||
echo "Exiting."
|
||||
|
||||
@@ -5,14 +5,22 @@ ou: superheros
|
||||
uidNumber: 5001
|
||||
accountStatus: active
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/hackers
|
||||
description: hackers via LDAP
|
||||
gecos: hackers via LDAP
|
||||
description: hackers
|
||||
gecos: hackers
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
dn: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: johndoe
|
||||
@@ -24,14 +32,22 @@ uidNumber: 5002
|
||||
accountStatus: active
|
||||
mail: jdoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /root
|
||||
description: johndoe via LDAP
|
||||
gecos: johndoe via LDAP
|
||||
description: johndoe
|
||||
gecos: johndoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
|
||||
w3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4h
|
||||
C/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLV
|
||||
@@ -49,16 +65,24 @@ uidNumber: 5003
|
||||
accountStatus: active
|
||||
mail: jdoe2@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/jamesdoe
|
||||
description: jamesdoe via LDAP
|
||||
gecos: jamesdoe via LDAP
|
||||
description: jamesdoe
|
||||
gecos: jamesdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
|
||||
w3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4h
|
||||
C/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLV
|
||||
@@ -76,16 +100,24 @@ uidNumber: 5004
|
||||
accountStatus: active
|
||||
mail: adoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/alexdoe
|
||||
description: alexdoe via LDAP
|
||||
gecos: alexdoe via LDAP
|
||||
description: alexdoe
|
||||
gecos: alexdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
dn: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: jackdoe
|
||||
@@ -97,16 +129,24 @@ uidNumber: 5005
|
||||
accountStatus: active
|
||||
mail: jdoe3@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/jackdoe
|
||||
description: jackdoe via LDAP
|
||||
gecos: jackdoe via LDAP
|
||||
description: jackdoe
|
||||
gecos: jackdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
dn: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: sarahdoe
|
||||
@@ -118,16 +158,24 @@ uidNumber: 5006
|
||||
accountStatus: active
|
||||
mail: sdoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/sarahdoe
|
||||
description: sarahdoe via LDAP
|
||||
gecos: sarahdoe via LDAP
|
||||
description: sarahdoe
|
||||
gecos: sarahdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
dn: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
cn: serviceuser
|
||||
@@ -136,11 +184,19 @@ ou: svcaccts
|
||||
uidNumber: 5003
|
||||
accountStatus: active
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/serviceuser
|
||||
description: serviceuser via LDAP
|
||||
gecos: serviceuser via LDAP
|
||||
description: serviceuser
|
||||
gecos: serviceuser
|
||||
gidNumber: 5502
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=svcaccts,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
dn: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
cn: superheros
|
||||
description: superheros via LDAP
|
||||
uid: superheros
|
||||
description: superheros
|
||||
gidNumber: 5501
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
@@ -18,7 +19,8 @@ memberUid: sarahdoe
|
||||
|
||||
dn: cn=svcaccts,ou=groups,dc=glauth,dc=com
|
||||
cn: svcaccts
|
||||
description: svcaccts via LDAP
|
||||
uid: svcaccts
|
||||
description: svcaccts
|
||||
gidNumber: 5502
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
@@ -26,7 +28,8 @@ memberUid: serviceuser
|
||||
|
||||
dn: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
cn: vpnaccess
|
||||
description: vpnaccess via LDAP
|
||||
uid: vpnaccess
|
||||
description: vpnaccess
|
||||
gidNumber: 5503
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
@@ -44,7 +47,8 @@ memberUid: sarahdoe
|
||||
|
||||
dn: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
cn: allaccs
|
||||
description: allaccs via LDAP
|
||||
uid: allaccs
|
||||
description: allaccs
|
||||
gidNumber: 5504
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
@@ -64,7 +68,8 @@ memberUid: serviceuser
|
||||
|
||||
dn: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
cn: mailadmin
|
||||
description: mailadmin via LDAP
|
||||
uid: mailadmin
|
||||
description: mailadmin
|
||||
gidNumber: 5505
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
@@ -78,13 +83,15 @@ memberUid: sarahdoe
|
||||
|
||||
dn: cn=webmail,ou=groups,dc=glauth,dc=com
|
||||
cn: webmail
|
||||
description: webmail via LDAP
|
||||
uid: webmail
|
||||
description: webmail
|
||||
gidNumber: 5506
|
||||
objectClass: posixGroup
|
||||
|
||||
dn: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
cn: fulltime
|
||||
description: fulltime via LDAP
|
||||
uid: fulltime
|
||||
description: fulltime
|
||||
gidNumber: 5507
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
|
||||
@@ -5,12 +5,20 @@ ou: superheros
|
||||
uidNumber: 5001
|
||||
accountStatus: active
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/hackers
|
||||
description: hackers via LDAP
|
||||
gecos: hackers via LDAP
|
||||
description: hackers
|
||||
gecos: hackers
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
|
||||
@@ -8,14 +8,22 @@ uidNumber: 5002
|
||||
accountStatus: active
|
||||
mail: jdoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /root
|
||||
description: johndoe via LDAP
|
||||
gecos: johndoe via LDAP
|
||||
description: johndoe
|
||||
gecos: johndoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
|
||||
w3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4h
|
||||
C/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLV
|
||||
|
||||
@@ -5,11 +5,19 @@ ou: svcaccts
|
||||
uidNumber: 5003
|
||||
accountStatus: active
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/serviceuser
|
||||
description: serviceuser via LDAP
|
||||
gecos: serviceuser via LDAP
|
||||
description: serviceuser
|
||||
gecos: serviceuser
|
||||
gidNumber: 5502
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=svcaccts,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
|
||||
@@ -8,16 +8,24 @@ uidNumber: 5003
|
||||
accountStatus: active
|
||||
mail: jdoe2@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/jamesdoe
|
||||
description: jamesdoe via LDAP
|
||||
gecos: jamesdoe via LDAP
|
||||
description: jamesdoe
|
||||
gecos: jamesdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
|
||||
w3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4h
|
||||
C/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLV
|
||||
|
||||
@@ -8,14 +8,22 @@ uidNumber: 5004
|
||||
accountStatus: active
|
||||
mail: adoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/alexdoe
|
||||
description: alexdoe via LDAP
|
||||
gecos: alexdoe via LDAP
|
||||
description: alexdoe
|
||||
gecos: alexdoe
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
shadowExpire: -1
|
||||
shadowFlag: 134538308
|
||||
shadowInactive: -1
|
||||
shadowLastChange: 11000
|
||||
shadowMax: 99999
|
||||
shadowMin: -1
|
||||
shadowWarning: 7
|
||||
|
||||
|
||||
Reference in New Issue
Block a user