mirror of
https://github.com/glauth/glauth.git
synced 2026-10-27 06:26:59 +00:00
Compare commits
112 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 | |||
| 28802ff87d | |||
| 75fa168e9a | |||
| 7a573aad93 | |||
| 9f349d10c8 | |||
| a02544ac8b | |||
| 3acd492e61 | |||
| bd9a5e47ee | |||
| 2e70a47dd6 | |||
| e546a1a289 | |||
| c550eb60e9 | |||
| 2b4aa3b78d | |||
| 6fbd8105cd | |||
| a03a26960d | |||
| 45a2671f46 | |||
| 5e1ed70865 | |||
| 3a13387be2 | |||
| e606ecbb56 | |||
| d9183b8f74 | |||
| 72c8e84a0d | |||
| c162ed8366 | |||
| 986a9187ee | |||
| a835e569cd | |||
| d410b01955 | |||
| ff6cb9a9d5 | |||
| 6516741b3c | |||
| 5c1a39bd47 | |||
| a21d93bbd7 | |||
| 3a513c4ee6 | |||
| 6a3f96c04d | |||
| 2020579614 | |||
| 47401aac5f | |||
| 6cd27aa790 | |||
| 9495943701 | |||
| 8de56c87a9 | |||
| 208fb1e33d | |||
| 32a13ec85a | |||
| a75743ef7d | |||
| fef26916e9 | |||
| 8e48638f05 | |||
| 93a6a5542f | |||
| c431a6e426 | |||
| a9837f3112 | |||
| 2d7a2ab8ce | |||
| bc94f47c9e | |||
| 39b50ec92a | |||
| 50a78e3c56 | |||
| 5e2114e04b | |||
| 706c144f82 | |||
| 5519634a3e | |||
| b32db95f4f | |||
| 0b1c979247 | |||
| b04ff2dd67 | |||
| 94574e50a7 | |||
| 1155d214ed | |||
| 9ae4613a7f | |||
| 8d808c80a0 | |||
| 5728484e61 | |||
| 6df835dd68 | |||
| 80883e4a57 | |||
| 02970c82ff | |||
| 7124c108a7 | |||
| 136d7d7833 | |||
| f0cf6488e4 | |||
| 1876eb9ff7 | |||
| dc90cd6819 | |||
| c2aca63246 | |||
| 60ac4368c6 | |||
| 64fbf20dcd | |||
| de571e8cb6 | |||
| c232ee0476 | |||
| 4f82bf2baf | |||
| 7608bc0133 | |||
| 2ae84a9034 | |||
| ac85255d86 | |||
| 2a16b13a01 | |||
| 5b84deb1c1 | |||
| c5cc0f5572 | |||
| dd71d9ac4e | |||
| 5f5c12e78d | |||
| d5a1b6f216 | |||
| 82bff3fc36 | |||
| fd9aaa243d | |||
| e2c9210beb | |||
| e03e9216fa | |||
| 70ff3600ec | |||
| 68edcdc228 | |||
| f076f868dc | |||
| f039bbb964 |
@@ -0,0 +1,25 @@
|
||||
Thank you for making a pull request!
|
||||
|
||||
A few things to be aware of as you're working on your PR:
|
||||
|
||||
## WIP Tag
|
||||
Incomplete PRs are more than welcome - it can be useful to collaborate before
|
||||
implementation of an idea is complete. However, if your PR is not ready
|
||||
for merge, please add [WIP] to the end of the title (work-in-progress).
|
||||
|
||||
## Tests
|
||||
Before committing, you are encouraged to run the small but growing test
|
||||
suite. This is accomplished by `make test`. Additionally, if you are adding
|
||||
new functionality, consider adding tests covering your feature.
|
||||
|
||||
## CI
|
||||
Each push to a branch connected to a PR will be run through GLAuth's
|
||||
CI system. Please use these to your advantage. In particular, the Travis
|
||||
integration tests rely on the LDAP queries returning with a set result,
|
||||
so if your changes will change the output, Travis will likely fail.
|
||||
|
||||
To update, run `make fast && make updatetest && make test`. This will
|
||||
delete the output snapshots provided and make new ones. You can then
|
||||
inspect the changes and commit them.
|
||||
|
||||
Similarly, check codeclimate and try to fix what you find there if it fails.
|
||||
+13
@@ -4,4 +4,17 @@ glauth.cfg
|
||||
# Ignore the built binary during testing
|
||||
glauth
|
||||
|
||||
# Ignore generated certs in this folder
|
||||
certs/
|
||||
|
||||
# Built binaries
|
||||
bin
|
||||
|
||||
pkg/assets/bindata.go
|
||||
|
||||
.*.swp
|
||||
|
||||
# Ignoring travis CLI tool logs
|
||||
*travis.log
|
||||
|
||||
certs/
|
||||
+41
-11
@@ -1,9 +1,12 @@
|
||||
language: go
|
||||
|
||||
# USE THIS alongside makefile tag detection: $TRAVIS_TAG
|
||||
|
||||
# if you change the version here, also update the travis deploy condition
|
||||
go:
|
||||
- 1.3.x # Test an old version, to be sure it works
|
||||
- 1.9.x # last version
|
||||
- 1.10.x # current version
|
||||
- 1.11.x # older version
|
||||
- 1.12.x # last version
|
||||
- 1.13.x # current version
|
||||
|
||||
# Speed up git clones
|
||||
git:
|
||||
@@ -14,20 +17,27 @@ git:
|
||||
# flunk the build and immediately stop. It's sorta like having
|
||||
# set -e enabled in bash.
|
||||
before_script:
|
||||
- GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) # All the .go files, excluding vendor/
|
||||
#- go get github.com/golang/lint/golint # Linter
|
||||
#- go get honnef.co/go/tools/cmd/megacheck # Badass static analyzer/linter
|
||||
# - go get github.com/fzipp/gocyclo
|
||||
- travis_retry sudo apt-get -qq update && sudo apt-get install -y ldap-utils oathtool # need this for integration tests
|
||||
- travis_retry sudo apt-get -qq update && sudo apt-get install -y go-bindata # need this for makefile
|
||||
- GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) # All the .go files, excluding vendor/
|
||||
#- travis_retry go get github.com/golang/lint/golint # Linter
|
||||
#- travis_retry go get honnef.co/go/tools/cmd/megacheck # Badass static analyzer/linter
|
||||
# - travis_retry go get github.com/fzipp/gocyclo # Not sure what this is
|
||||
|
||||
|
||||
# Run bindata for assets
|
||||
- make bindata
|
||||
|
||||
# Fetch dependencies
|
||||
- go get -t -v ./...
|
||||
- travis_retry go get -t -v ./...
|
||||
|
||||
|
||||
script:
|
||||
- env # Dump the env to make debugging easier
|
||||
- diff -u <(echo -n) <(gofmt -d ./) # Fail if a .go file hasn't been formatted with 'go fmt'
|
||||
# - go vet ./... " # go vet is the official Go static analyzer
|
||||
|
||||
# - go test -v -race ./... # Run all the tests with the race detector enabled
|
||||
# - go vet ./... # go vet is the official Go static analyzer
|
||||
# - megacheck ./... # "go vet on steroids" + linter
|
||||
# - gocyclo -over 19 $GO_FILES # forbid code with huge functions
|
||||
# - golint -set_exit_status $(go list ./...) # one last linter
|
||||
@@ -39,11 +49,31 @@ script:
|
||||
# - go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
# Full compile test
|
||||
- go build -o ./glauth glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
|
||||
# - go build -o ./glauth glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
|
||||
- make all
|
||||
|
||||
# Output hashes to the build log
|
||||
- cat bin/*.sha256
|
||||
|
||||
# Integration test with compiled binary
|
||||
- scripts/travis/integration-test.sh "scripts/travis/test-config.cfg"
|
||||
- make test
|
||||
|
||||
|
||||
|
||||
# after_success:
|
||||
# - bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
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"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
condition: $TRAVIS_GO_VERSION =~ ^1\.13
|
||||
repo: glauth/glauth
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
|
||||
|
||||
For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage.
|
||||
+14
-7
@@ -15,21 +15,25 @@ 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
|
||||
RUN apk add --no-cache git bzr make
|
||||
|
||||
# Install deps
|
||||
RUN go get -d -v ./...
|
||||
|
||||
# Build
|
||||
RUN go build -o /app/glauth glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
|
||||
# Run go-bindata to embed data for API
|
||||
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
|
||||
|
||||
#################
|
||||
# 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
|
||||
@@ -42,11 +46,14 @@ COPY --from=build /app/glauth /app/glauth
|
||||
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 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 5555
|
||||
EXPOSE 389 636 5555
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||
CMD ["/bin/sh", "/app/docker/start.sh"]
|
||||
|
||||
|
||||
@@ -1,22 +1,95 @@
|
||||
VERSION=$(shell bin/glauth64 --version)
|
||||
|
||||
GIT_COMMIT=$(shell git rev-list -1 HEAD )
|
||||
BUILD_TIME=$(shell date --utc +%Y%m%d_%H%M%SZ)
|
||||
GIT_CLEAN=$(shell git status | grep -E "working (tree|directory) clean" | wc -l)
|
||||
|
||||
run:
|
||||
go run glauth.go bindata.go ldapbackend.go webapi.go configbackend.go -c glauth.cfg
|
||||
# Last git tag
|
||||
LAST_GIT_TAG=$(shell git describe --abbrev=0 --tags 2> /dev/null)
|
||||
|
||||
all: bindata binaries
|
||||
# this=1 if the current commit is the tagged commit (ie, if this is a release build)
|
||||
GIT_IS_TAG_COMMIT=$(shell git describe --abbrev=0 --tags > /dev/null 2> /dev/null && echo "1" || echo "0")
|
||||
|
||||
binaries: bindata linux32 linux64 darwin64
|
||||
# Used when a tag isn't available
|
||||
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
|
||||
TRIM_FLAGS=-gcflags "all=-trimpath=${PWD}" -asmflags "all=-trimpath=${PWD}"
|
||||
|
||||
#####################
|
||||
# High level commands
|
||||
#####################
|
||||
|
||||
# Build and run - used for development
|
||||
run: setup devrun cleanup
|
||||
|
||||
# Run the integration test on linux64 (eventually allow the binary to be set)
|
||||
test: runtest
|
||||
|
||||
# Run build process for all binaries
|
||||
all: setup binaries verify cleanup
|
||||
|
||||
# Run build process for only linux64
|
||||
fast: setup linux64 verify cleanup
|
||||
|
||||
# list of binary formats to build
|
||||
binaries: linux32 linux64 linuxarm32 linuxarm64 darwin64 win32 win64
|
||||
|
||||
# Setup commands to always run
|
||||
setup: getdeps bindata format
|
||||
|
||||
#####################
|
||||
# Subcommands
|
||||
#####################
|
||||
|
||||
# Run integration test
|
||||
runtest:
|
||||
./scripts/travis/integration-test.sh cleanup
|
||||
|
||||
# Get all dependencies
|
||||
getdeps:
|
||||
go get -d ./...
|
||||
|
||||
updatetest:
|
||||
./scripts/travis/integration-test.sh
|
||||
|
||||
bindata:
|
||||
go-bindata -pkg=main assets
|
||||
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
|
||||
|
||||
linux32: bindata
|
||||
GOOS=linux GOARCH=386 go build -o bin/glauth32 glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
|
||||
|
||||
linux64: bindata
|
||||
GOOS=linux GOARCH=amd64 go build -o bin/glauth64 glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
|
||||
cleanup:
|
||||
rm pkg/assets/bindata.go
|
||||
|
||||
darwin64: bindata
|
||||
GOOS=darwin GOARCH=amd64 go build -o bin/glauthOSX glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
|
||||
format:
|
||||
go fmt
|
||||
|
||||
devrun:
|
||||
go run ${BUILD_FILES} -c glauth.cfg
|
||||
|
||||
|
||||
linux32:
|
||||
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 ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth64 ${BUILD_FILES} && cd bin && sha256sum glauth64 > glauth64.sha256
|
||||
|
||||
linuxarm32:
|
||||
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 ${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 ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauthOSX ${BUILD_FILES} && cd bin && sha256sum glauthOSX > glauthOSX.sha256
|
||||
|
||||
win32:
|
||||
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 ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win64 ${BUILD_FILES} && cd bin && sha256sum glauth-win64 > glauth-win64.sha256
|
||||
|
||||
|
||||
verify:
|
||||
cd bin && sha256sum *.sha256 -c && cd ../;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# GLAuth: LDAP authentication server for developers
|
||||
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
|
||||
|
||||
@@ -6,11 +5,13 @@ Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ co
|
||||
[](https://github.com/glauth/glauth/graphs/commit-activity)
|
||||
|
||||
[](https://hub.docker.com/r/glauth/glauth/)
|
||||
[](https://hub.docker.com/r/glauth/glauth/)
|
||||
[](https://hub.docker.com/r/glauth/glauth/)
|
||||
|
||||
[](https://codeclimate.com/github/glauth/glauth/maintainability)
|
||||
[](https://codeclimate.com/github/glauth/glauth/test_coverage)
|
||||
|
||||
[](http://paypal.me/benyanke)
|
||||
|
||||
* Centrally manage accounts across your infrastructure
|
||||
* Centrally manage SSH keys, Linux accounts, and passwords for cloud servers.
|
||||
* Lightweight alternative to OpenLDAP and Active Directory for development, or a homelab.
|
||||
@@ -32,6 +33,19 @@ This quickstart is a great way to try out GLAuth in a non-production environment
|
||||
- For example: `ldapsearch -LLL -H ldap://localhost:389 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bdc=glauth,dc=com cn=hackers`
|
||||
|
||||
|
||||
|
||||
### Make Commands
|
||||
|
||||
Note - makefile uses git data to inject build-time variables. For best results, run in the context of the git repo.
|
||||
|
||||
*make all* - run build binaries for platforms
|
||||
|
||||
*make fast* - run build for only linux 64 bit
|
||||
|
||||
*make run* - wrapper for the 'go run' command, setting up the needed tooling
|
||||
|
||||
*make test* - run the integration test on linux64 binary
|
||||
|
||||
### Usage:
|
||||
```
|
||||
glauth: securely expose your LDAP for external auth
|
||||
@@ -80,19 +94,23 @@ In order to use S3, you must set your AWS credentials. Either:
|
||||
1. set the -K and -S command-line flags **OR**
|
||||
2. set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.
|
||||
|
||||
More configuration options are documented here: https://github.com/nmcclain/glauth/blob/master/sample-simple.cfg
|
||||
More configuration options are documented here: https://github.com/glauth/glauth/blob/master/sample-simple.cfg
|
||||
|
||||
### Required Fields
|
||||
* Name
|
||||
* The user's username
|
||||
* ou
|
||||
* ID of the user's primary group
|
||||
* uidbumber
|
||||
* uidnumber
|
||||
* The user's unix user id
|
||||
* sshPublicKey
|
||||
* Specify an array of public keys
|
||||
|
||||
### Optional Fields
|
||||
* otherGroups
|
||||
* Array of IDs of groups the user is a member of.
|
||||
* Example: [5501, 5002]
|
||||
* default = blank
|
||||
* givenname
|
||||
* First name
|
||||
* Example: John
|
||||
@@ -117,11 +135,48 @@ More configuration options are documented here: https://github.com/nmcclain/glau
|
||||
* Specify an overridden home directory for the user
|
||||
* Example: /home/itadmin
|
||||
* default = /home/[username]
|
||||
* otpsecret
|
||||
* Specify OTP secret used to validate OTP passcode
|
||||
* Example: 3hnvnk4ycv44glzigd6s25j4dougs3rk
|
||||
* default = blank
|
||||
* passappsha256
|
||||
* Specify an array of app passwords which can also succesfully bind - these bypass the OTP check. Hash the same way as password.
|
||||
* Example: ["c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa","4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88"]
|
||||
* default = blank
|
||||
* yubikey
|
||||
* Specify Yubikey ID for maching Yubikey OTP against the user
|
||||
* Example: cccjgjgkhcbb
|
||||
* default = blank
|
||||
|
||||
|
||||
### OpenSSH keys:
|
||||
GLAuth can store a user's SSH authorized keys. Add one or more keys per user as shown above, then setup the goklp helper: https://github.com/appliedtrust/goklp
|
||||
|
||||
|
||||
### Two Factor Authentication
|
||||
GLAuth can be configured to accept OTP tokens as appended to a users password. Support is added for both **TOTP tokens** (often known by it's most prominent implementation, "Google Authenticator") and **Yubikey OTP tokens**.
|
||||
|
||||
When using 2FA, append the 2FA code to the end of the password when authenticating. For example, if your password is "monkey" and your otp is "123456", enter "monkey123456" as your password.
|
||||
|
||||
#### TOTP Configuration
|
||||
To enable TOTP authentication on a user, you can use a tool [like this](https://freeotp.github.io/qrcode.html) to generate a QR code (pick 'Timeout' and optionally let it generate a random secret for you), which can be scanned and used with the [Google Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en) app. To enable TOTP authentication, configure the `otpsecret` for the user with the TOTP secret.
|
||||
|
||||
#### App Passwords
|
||||
Additionally, you can specify an array of password hashes using the `passappsha256` for app passwords. These are not OTP validated, and are hashed in the same way as a password. This allows you to generate a long random string to be used in software which requires the ability to authenticate.
|
||||
|
||||
However, app passwords can be used without OTP as well.
|
||||
|
||||
#### Yubikey Configuration
|
||||
For Yubikey OTP token authentication, first [configure your Yubikey](https://www.yubico.com/products/services-software/personalization-tools/yubikey-otp/). After this, make sure to [request a `Client ID` and `Secret key` pair](https://upgrade.yubico.com/getapikey/).
|
||||
|
||||
Now configure the the `yubikeyclientid` and `yubikeysecret` fields in the general section in the configuration file.
|
||||
|
||||
To enable Yubikey OTP authentication for a user, you must specify their Yubikey ID on the users `yubikey` field. The Yubikey ID is the first 12 characters of the Yubikey OTP, as explained in the below chart.
|
||||
|
||||

|
||||
|
||||
When a user has been configured with either one of the OTP options, the OTP authentication is required for the user. If both are configured, either one will work.
|
||||
|
||||
### Backends:
|
||||
For advanced users, GLAuth supports pluggable backends. Currently, it can use a local file, S3 or an existing LDAP infrastructure. In the future, we hope to have backends that support Mongo, SQL, and other datastores.
|
||||
```toml
|
||||
@@ -134,9 +189,32 @@ For advanced users, GLAuth supports pluggable backends. Currently, it can use a
|
||||
Any of the architectures above will work for production. Just remember:
|
||||
|
||||
* Always use legit SSL certs for production!
|
||||
|
||||
# Other Architectures
|
||||
A small note about other architectures: while I expect the code is, for the most part, system-independent, there is not a good (and free) CI system which can be easily used to continuously test releases on ARM, BSD, Linux-32bit, and Windows. As such, all of the non-linux-64bit packages are provided as is. The extent of testing on these packages consists solely of cross-compiling for these architectures from a linux 64 bit system.
|
||||
|
||||
We will accept PRs which fix bugs on these platforms, but be aware these binaries will not be tested regularly, and instead are provided for the convenience of those who feel comfortable with this.
|
||||
|
||||
### Building:
|
||||
You'll need go-bindata to build GLAuth:
|
||||
You'll need go-bindata to build GLAuth. Then use the Makefile.
|
||||
```unix
|
||||
go get github.com/jteeuwen/go-bindata/...
|
||||
make all
|
||||
```
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starcharts.herokuapp.com/glauth/glauth)
|
||||
|
||||
### Support
|
||||
|
||||
Support the ongoing development of GLAuth!
|
||||
|
||||
**Paypal**
|
||||
|
||||
[](http://paypal.me/benyanke)
|
||||
|
||||
|
||||
**Bitcoin Address**
|
||||
|
||||
39z2Zkoc24LsuiqCQNFe7QrX4da3mzbGjK
|
||||
|
||||
-221
@@ -1,221 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func bindata_read(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func assets_glauth_css() ([]byte, error) {
|
||||
return bindata_read([]byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0x8c, 0x8f,
|
||||
0xc1, 0x72, 0x83, 0x20, 0x14, 0x45, 0xf7, 0x7c, 0xc5, 0x9b, 0x71, 0x1d,
|
||||
0x47, 0x0d, 0xa6, 0x60, 0xbe, 0x86, 0x07, 0x4f, 0xa5, 0x35, 0xe0, 0x00,
|
||||
0x69, 0x3b, 0xed, 0xf4, 0xdf, 0x1b, 0xcd, 0x98, 0x29, 0x71, 0xd3, 0x2d,
|
||||
0xf7, 0xdc, 0x7b, 0x78, 0xac, 0x7c, 0xbd, 0x5e, 0xd0, 0xa7, 0xe0, 0x1d,
|
||||
0x7c, 0x33, 0x00, 0xed, 0x27, 0x1f, 0x3a, 0x28, 0x74, 0x75, 0x94, 0x0d,
|
||||
0x9e, 0x6f, 0x2f, 0xa8, 0xf4, 0xdb, 0x10, 0xfc, 0xd5, 0x99, 0xc3, 0x16,
|
||||
0x92, 0xee, 0xab, 0xbe, 0x3e, 0xb3, 0x1f, 0xc6, 0x8a, 0x98, 0x54, 0x8a,
|
||||
0xb3, 0x72, 0x34, 0x65, 0xfd, 0x23, 0xe7, 0xb2, 0xa5, 0x1d, 0x52, 0x5e,
|
||||
0x28, 0x05, 0xab, 0x9f, 0x50, 0x29, 0x0c, 0xfe, 0x41, 0xb3, 0x54, 0x62,
|
||||
0x2b, 0xf1, 0xb4, 0x7c, 0x64, 0x56, 0xc6, 0x58, 0x37, 0x74, 0x20, 0xe6,
|
||||
0xcf, 0x85, 0x2e, 0x22, 0x85, 0x77, 0x0a, 0xff, 0xc5, 0xd9, 0xd8, 0x2c,
|
||||
0xe4, 0x06, 0x36, 0x52, 0x54, 0x28, 0xef, 0xd6, 0xa4, 0xec, 0x04, 0x69,
|
||||
0xcc, 0x86, 0x5e, 0x7a, 0xa1, 0x85, 0x59, 0x3d, 0xf7, 0xd8, 0xac, 0xf1,
|
||||
0x63, 0x95, 0x2f, 0xab, 0xfb, 0x7b, 0x01, 0x7a, 0xef, 0xd2, 0x21, 0xda,
|
||||
0x2f, 0xea, 0xa0, 0xde, 0xd4, 0xdb, 0x44, 0xe9, 0xe7, 0x4c, 0x22, 0x88,
|
||||
0x73, 0x65, 0x1e, 0xad, 0x0f, 0xb2, 0xc3, 0x98, 0x3a, 0x40, 0x3f, 0x65,
|
||||
0xe6, 0x5b, 0xad, 0x44, 0xeb, 0x4c, 0xd6, 0xad, 0x4f, 0xaa, 0x12, 0xed,
|
||||
0x33, 0x16, 0x49, 0x05, 0x9d, 0x5f, 0x52, 0x2b, 0xd4, 0x52, 0xaf, 0xff,
|
||||
0xf8, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x91, 0x33, 0xcc, 0x6e, 0xee, 0x01,
|
||||
0x00, 0x00,
|
||||
},
|
||||
"assets/glauth.css",
|
||||
)
|
||||
}
|
||||
|
||||
func assets_glauth_js() ([]byte, error) {
|
||||
return bindata_read([]byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0xa4, 0x54,
|
||||
0x5d, 0x6b, 0xdb, 0x30, 0x14, 0x7d, 0xcf, 0xaf, 0xb8, 0xf3, 0x02, 0x95,
|
||||
0x59, 0x26, 0x87, 0xbd, 0x86, 0xee, 0xad, 0xd0, 0x0e, 0xd6, 0xc1, 0x96,
|
||||
0x3f, 0xa0, 0x58, 0xd7, 0x89, 0x98, 0x3e, 0x8c, 0x2c, 0x27, 0x1b, 0xc5,
|
||||
0xff, 0x7d, 0x57, 0x96, 0x9d, 0xb8, 0x69, 0x0b, 0x1d, 0x7e, 0x88, 0x13,
|
||||
0xae, 0xce, 0x39, 0x3e, 0xbe, 0xe7, 0xc4, 0x47, 0xe1, 0x41, 0xd9, 0x80,
|
||||
0xfe, 0x28, 0x34, 0xdc, 0xc2, 0x97, 0xf5, 0x7a, 0xbd, 0x59, 0x2c, 0x96,
|
||||
0xac, 0x6a, 0x6d, 0x19, 0x94, 0xb3, 0x2c, 0x87, 0xa7, 0x05, 0x40, 0x5b,
|
||||
0x4b, 0x11, 0xf0, 0x3b, 0x06, 0xaf, 0xca, 0x86, 0xe5, 0x1b, 0x1a, 0x15,
|
||||
0x45, 0x1a, 0x6e, 0x85, 0xd2, 0x71, 0xd2, 0xd1, 0x67, 0x11, 0xe5, 0x02,
|
||||
0x0d, 0x1e, 0x24, 0x89, 0x45, 0xa5, 0x51, 0xe7, 0x5a, 0xa1, 0x57, 0x8d,
|
||||
0x68, 0x93, 0x26, 0x04, 0x5f, 0xf2, 0x3d, 0x86, 0x6f, 0xbf, 0x7e, 0x3c,
|
||||
0x32, 0xc8, 0x0a, 0x89, 0xbb, 0x76, 0x5f, 0x10, 0xa0, 0xc9, 0x56, 0x70,
|
||||
0x36, 0x23, 0x13, 0x2f, 0x31, 0x03, 0x9a, 0x5a, 0x93, 0x26, 0x51, 0xef,
|
||||
0x85, 0x95, 0x1a, 0x77, 0x84, 0xe6, 0xa5, 0x33, 0xb5, 0xd2, 0xc8, 0x60,
|
||||
0xc9, 0xb2, 0x8f, 0x4d, 0x10, 0xa1, 0xa9, 0x85, 0x45, 0xfd, 0x79, 0x44,
|
||||
0x67, 0x39, 0x3f, 0x04, 0x43, 0x8e, 0xa1, 0x7f, 0x8a, 0x24, 0x15, 0x27,
|
||||
0xf1, 0xf7, 0xed, 0x59, 0x94, 0x6e, 0x95, 0x8e, 0x9f, 0xcb, 0x8c, 0xec,
|
||||
0x78, 0x99, 0xf0, 0xdf, 0x6b, 0x65, 0xbe, 0x8b, 0x19, 0x06, 0x28, 0x63,
|
||||
0xf4, 0x33, 0x2d, 0x24, 0x8d, 0x17, 0x26, 0xba, 0x9c, 0x2e, 0xbc, 0x8a,
|
||||
0x4d, 0xb8, 0x2a, 0xce, 0x5b, 0x0b, 0xcc, 0xee, 0xbc, 0x77, 0xfe, 0x43,
|
||||
0x36, 0xa1, 0x0b, 0x7d, 0x12, 0x7f, 0x9b, 0x97, 0x02, 0x0d, 0x86, 0xad,
|
||||
0x32, 0xe8, 0xda, 0xc0, 0x9e, 0x95, 0x68, 0x35, 0xf6, 0x76, 0xd0, 0xa0,
|
||||
0x0a, 0x5e, 0xd7, 0x2d, 0x75, 0xf3, 0xdc, 0xb5, 0xd8, 0xcc, 0xeb, 0xa2,
|
||||
0xc5, 0x59, 0x91, 0xc1, 0xa7, 0xb1, 0xb6, 0xaf, 0xb5, 0x4d, 0xf2, 0xca,
|
||||
0xf9, 0x3b, 0x51, 0x1e, 0x2e, 0xe6, 0x70, 0x3c, 0x03, 0x50, 0x15, 0x30,
|
||||
0xe4, 0x44, 0xfd, 0x3a, 0x68, 0x5c, 0x8e, 0xe0, 0xf2, 0x67, 0x88, 0x88,
|
||||
0xcd, 0x30, 0xee, 0xfa, 0xef, 0xee, 0xff, 0xf2, 0x8b, 0x4a, 0x33, 0xc2,
|
||||
0xeb, 0xb3, 0xe2, 0x07, 0x25, 0x91, 0xe5, 0xbc, 0xf6, 0x58, 0xa3, 0x95,
|
||||
0x5b, 0x37, 0xe8, 0x92, 0x5c, 0x25, 0x24, 0x3e, 0x58, 0x16, 0x5f, 0x00,
|
||||
0xef, 0x8a, 0x74, 0xa0, 0xcd, 0x0e, 0x33, 0x46, 0xf4, 0x5a, 0x92, 0x8b,
|
||||
0xc9, 0x1e, 0x3c, 0xee, 0x55, 0x43, 0x88, 0x7b, 0xd4, 0x35, 0x7a, 0x76,
|
||||
0x13, 0x79, 0x3f, 0x91, 0x1e, 0x50, 0x1d, 0xf1, 0x66, 0x12, 0x58, 0xe9,
|
||||
0x48, 0xe7, 0x4f, 0x58, 0xc1, 0x4e, 0xbb, 0xf2, 0x77, 0xba, 0x65, 0x4c,
|
||||
0xe7, 0xa4, 0xac, 0x74, 0x27, 0x6e, 0x9c, 0x41, 0x1b, 0x46, 0x27, 0x45,
|
||||
0xe1, 0x31, 0xb4, 0xde, 0x42, 0x1a, 0x8f, 0x64, 0xda, 0x84, 0x77, 0xe6,
|
||||
0xd1, 0x9d, 0xd8, 0xb0, 0xba, 0xb7, 0x50, 0xce, 0x1b, 0x11, 0x12, 0xa8,
|
||||
0x43, 0xdd, 0xe0, 0xd3, 0x14, 0x3d, 0xc0, 0xfa, 0xc3, 0xe1, 0xd5, 0xf8,
|
||||
0x2f, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x12, 0x43, 0xad, 0x6a, 0x05, 0x00,
|
||||
0x00,
|
||||
},
|
||||
"assets/glauth.js",
|
||||
)
|
||||
}
|
||||
|
||||
func assets_index_html() ([]byte, error) {
|
||||
return bindata_read([]byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0xb4, 0x57,
|
||||
0xeb, 0x6e, 0xdb, 0x36, 0x14, 0xfe, 0x9f, 0xa7, 0x60, 0xb4, 0xbf, 0x93,
|
||||
0x34, 0xa7, 0xeb, 0x96, 0x15, 0xb2, 0x81, 0x2e, 0xcd, 0xd6, 0x00, 0xed,
|
||||
0x1a, 0x24, 0x19, 0xb6, 0xa1, 0x28, 0x0a, 0x9a, 0x3c, 0xb6, 0xe8, 0x50,
|
||||
0xa2, 0x42, 0x52, 0x8e, 0x0d, 0xc1, 0xef, 0xbe, 0x43, 0x51, 0xb6, 0x25,
|
||||
0xcf, 0xf1, 0x0d, 0x2b, 0x10, 0xc4, 0xbc, 0x7c, 0xe7, 0x7e, 0xe1, 0x51,
|
||||
0x72, 0xfe, 0xee, 0xd3, 0xd5, 0xc3, 0x3f, 0xb7, 0xd7, 0x24, 0xb5, 0x99,
|
||||
0x1c, 0x9c, 0x25, 0xee, 0x87, 0x48, 0x9a, 0x8f, 0xfb, 0x01, 0xe4, 0xc1,
|
||||
0xe0, 0x8c, 0x90, 0x24, 0x05, 0xca, 0xdd, 0x02, 0x97, 0x19, 0x58, 0x4a,
|
||||
0x58, 0x4a, 0xb5, 0x01, 0xdb, 0x0f, 0x4a, 0x3b, 0x0a, 0x2f, 0x83, 0xf6,
|
||||
0x55, 0x6a, 0x6d, 0x11, 0xc2, 0x53, 0x29, 0xa6, 0xfd, 0xe0, 0xef, 0xf0,
|
||||
0xcf, 0xb7, 0xe1, 0x95, 0xca, 0x0a, 0x6a, 0xc5, 0x50, 0x42, 0x40, 0x98,
|
||||
0xca, 0x2d, 0xe4, 0x48, 0x77, 0x73, 0xdd, 0x07, 0x3e, 0x86, 0x0e, 0x65,
|
||||
0x4e, 0x33, 0xe8, 0x07, 0x53, 0x01, 0xcf, 0x85, 0xd2, 0xb6, 0x05, 0x7e,
|
||||
0x16, 0xdc, 0xa6, 0x7d, 0x0e, 0x53, 0xc1, 0x20, 0xac, 0x37, 0xdf, 0x13,
|
||||
0x91, 0x0b, 0x2b, 0xa8, 0x0c, 0x0d, 0xa3, 0x12, 0xfa, 0xbd, 0x2d, 0x8c,
|
||||
0x38, 0x18, 0xa6, 0x45, 0x61, 0x85, 0xca, 0x5b, 0xbc, 0xb6, 0x00, 0x69,
|
||||
0x69, 0x53, 0xa5, 0xbb, 0x18, 0x0f, 0xb2, 0xc2, 0x4a, 0x18, 0xfc, 0xfe,
|
||||
0xe1, 0x2d, 0x22, 0xc8, 0xbd, 0xa5, 0xb6, 0x34, 0x49, 0xec, 0x0f, 0x1b,
|
||||
0x84, 0x14, 0xf9, 0x23, 0xd1, 0x20, 0xfb, 0x81, 0xb1, 0x73, 0x09, 0x26,
|
||||
0x05, 0x40, 0xcd, 0x53, 0x0d, 0xa3, 0x7e, 0xe0, 0x3c, 0x61, 0xde, 0xc4,
|
||||
0x71, 0x46, 0x67, 0x8c, 0xe7, 0xd1, 0x50, 0x29, 0x6b, 0xac, 0xa6, 0x85,
|
||||
0xdb, 0x30, 0x95, 0xc5, 0xab, 0x83, 0xf8, 0x55, 0x74, 0x11, 0xfd, 0x10,
|
||||
0x33, 0x63, 0xd6, 0x67, 0x51, 0x26, 0x10, 0x65, 0xcc, 0x52, 0xdf, 0x6f,
|
||||
0x26, 0x28, 0xb4, 0x29, 0x64, 0x70, 0x8c, 0x38, 0x6a, 0x30, 0xf4, 0x26,
|
||||
0x1e, 0x4b, 0xe7, 0xb8, 0x86, 0xc8, 0x53, 0x9d, 0x87, 0x21, 0x79, 0xff,
|
||||
0xf0, 0xf1, 0xc3, 0x6b, 0x62, 0x52, 0x91, 0x11, 0x9a, 0x73, 0x72, 0x07,
|
||||
0xa6, 0x50, 0x39, 0x8f, 0x26, 0x86, 0xdc, 0x5c, 0x5f, 0x12, 0x53, 0x16,
|
||||
0x2e, 0xb8, 0x44, 0x8d, 0x1a, 0x20, 0x48, 0x94, 0x9e, 0x5b, 0x53, 0x83,
|
||||
0x33, 0xe0, 0x82, 0x92, 0xa7, 0x12, 0xb4, 0x00, 0x43, 0xc2, 0x70, 0xb0,
|
||||
0x62, 0xfb, 0x59, 0x8c, 0x88, 0xb4, 0xc8, 0x82, 0xfc, 0xf2, 0xc5, 0x9f,
|
||||
0xe2, 0xb9, 0x8f, 0x2f, 0x31, 0x9a, 0xad, 0x7d, 0xa0, 0x8c, 0x89, 0x1a,
|
||||
0x3f, 0x38, 0xd3, 0x5d, 0x2e, 0xbf, 0x46, 0x65, 0xa6, 0x68, 0xfa, 0xcf,
|
||||
0xd1, 0xc5, 0x7a, 0x5f, 0x1b, 0x3c, 0x41, 0xd5, 0x93, 0xd8, 0xb3, 0x39,
|
||||
0x86, 0xab, 0xf6, 0x46, 0xc5, 0xbd, 0xe8, 0x47, 0xe4, 0xd9, 0xec, 0x5e,
|
||||
0xe0, 0x98, 0x9c, 0x7f, 0x86, 0x9c, 0x8b, 0xd1, 0x17, 0x6f, 0x4e, 0x12,
|
||||
0xfb, 0x5a, 0x72, 0xcb, 0xa1, 0xe2, 0x73, 0xa2, 0x95, 0x74, 0xc9, 0xaa,
|
||||
0x58, 0xe9, 0xfc, 0xb0, 0x72, 0x25, 0x17, 0x53, 0xc2, 0x24, 0xba, 0xba,
|
||||
0x1f, 0xb8, 0xbc, 0xa4, 0x22, 0x07, 0x4d, 0xea, 0x50, 0x85, 0x26, 0x55,
|
||||
0xcf, 0x8c, 0x1a, 0x2c, 0x27, 0x4f, 0x9b, 0xe1, 0xe5, 0x92, 0xae, 0x09,
|
||||
0xc2, 0x47, 0x3c, 0x22, 0x93, 0x32, 0x1b, 0x2a, 0xab, 0x55, 0x4e, 0x46,
|
||||
0x4a, 0x13, 0x4a, 0x0a, 0x2d, 0x32, 0xaa, 0xe7, 0x04, 0xff, 0x3d, 0x82,
|
||||
0x15, 0xf9, 0x18, 0xfd, 0x6d, 0x0c, 0x1d, 0x03, 0xc1, 0x6b, 0x2c, 0x23,
|
||||
0x49, 0xac, 0x22, 0x94, 0xb9, 0x82, 0x59, 0xf9, 0xbe, 0xab, 0xc9, 0x8a,
|
||||
0x63, 0xb0, 0xbc, 0x5d, 0x21, 0x04, 0x77, 0xa9, 0x42, 0xad, 0x29, 0x68,
|
||||
0x0e, 0x32, 0x58, 0x52, 0xd4, 0x3b, 0x52, 0xff, 0x0f, 0x39, 0x8c, 0x68,
|
||||
0x89, 0x41, 0x2c, 0x4a, 0x29, 0x43, 0x2d, 0xc6, 0xa9, 0xed, 0xb2, 0x89,
|
||||
0x91, 0xcf, 0xfa, 0x20, 0x49, 0x7b, 0x4d, 0xe5, 0xa1, 0xcb, 0x7a, 0xad,
|
||||
0xf3, 0x62, 0xf0, 0x90, 0x0a, 0x83, 0xba, 0x2a, 0x49, 0xf0, 0x17, 0x0d,
|
||||
0xc5, 0xce, 0x00, 0x52, 0x15, 0xce, 0x7f, 0x24, 0x24, 0xb9, 0xb2, 0x98,
|
||||
0x42, 0xc2, 0x02, 0xe6, 0x2f, 0x45, 0x07, 0x3b, 0xe3, 0x0b, 0xad, 0x78,
|
||||
0x59, 0x5b, 0x76, 0x9e, 0xc4, 0xc5, 0xda, 0x57, 0x5e, 0xe4, 0x16, 0x4b,
|
||||
0xb5, 0x7a, 0x6e, 0x29, 0xd7, 0x8d, 0x86, 0x0c, 0x67, 0x26, 0xec, 0x5d,
|
||||
0x10, 0xb7, 0x32, 0x59, 0xf8, 0x53, 0xd7, 0x8a, 0xf4, 0x62, 0x50, 0xfb,
|
||||
0x01, 0xb5, 0xbe, 0x78, 0xd9, 0x4b, 0xc1, 0x1e, 0x97, 0x6e, 0xf8, 0xa2,
|
||||
0xa3, 0xe7, 0xd1, 0xfa, 0x0c, 0x29, 0x7b, 0xc4, 0x1c, 0x24, 0x06, 0xf4,
|
||||
0x14, 0xf4, 0x0e, 0xcd, 0xfc, 0xfd, 0x49, 0xba, 0xfd, 0x57, 0x51, 0xbf,
|
||||
0xf6, 0xf9, 0x18, 0xaf, 0x73, 0xd8, 0x65, 0xd6, 0x59, 0xbb, 0xd0, 0xba,
|
||||
0x99, 0x13, 0x5a, 0xc8, 0x0a, 0x49, 0x2d, 0x66, 0xb7, 0x9d, 0x17, 0x98,
|
||||
0xdd, 0x16, 0x66, 0x36, 0x9e, 0x85, 0x29, 0xf6, 0x07, 0x09, 0x43, 0x7c,
|
||||
0x76, 0xd6, 0x88, 0xed, 0x01, 0xf2, 0x6c, 0x5c, 0x6d, 0x75, 0xd4, 0x76,
|
||||
0x02, 0xf1, 0x6a, 0x89, 0xc2, 0xfe, 0xaf, 0x05, 0x0b, 0x06, 0x98, 0x19,
|
||||
0xb3, 0x79, 0xe4, 0xcc, 0xc6, 0xe4, 0x78, 0x83, 0xa5, 0x8b, 0x98, 0x01,
|
||||
0xa9, 0xaa, 0xce, 0xf9, 0x62, 0x91, 0x0c, 0xf5, 0x56, 0x93, 0xd7, 0xb5,
|
||||
0xbe, 0xdd, 0xa6, 0x53, 0xcc, 0x79, 0x59, 0xcf, 0xaf, 0x23, 0x5d, 0xbf,
|
||||
0x51, 0x3c, 0x1a, 0x8a, 0x9c, 0x7f, 0xd5, 0xf0, 0x64, 0x36, 0x55, 0xde,
|
||||
0x02, 0xe9, 0x68, 0x7f, 0x30, 0x73, 0x53, 0x32, 0x86, 0xed, 0x01, 0xf6,
|
||||
0x49, 0x58, 0xe1, 0x4e, 0x13, 0x03, 0x5a, 0x2b, 0xbd, 0x4f, 0x86, 0x07,
|
||||
0x1d, 0x2d, 0xc0, 0x00, 0xd5, 0x2c, 0xdd, 0xe3, 0xa6, 0x16, 0xe8, 0x54,
|
||||
0x01, 0x07, 0xb8, 0x6a, 0x13, 0x79, 0xaa, 0xa8, 0x7d, 0xee, 0xea, 0xc0,
|
||||
0x8e, 0x16, 0xc2, 0xa4, 0xda, 0x69, 0x85, 0xbf, 0x6f, 0xb1, 0xdd, 0x95,
|
||||
0xfe, 0xbe, 0x99, 0x9c, 0x52, 0x00, 0x55, 0xf5, 0x1d, 0x50, 0x96, 0x12,
|
||||
0x2f, 0xbd, 0xe9, 0x5e, 0x51, 0xc3, 0x70, 0xb1, 0x38, 0xdb, 0x59, 0xd0,
|
||||
0x55, 0x75, 0xcf, 0xdc, 0x33, 0xb9, 0x58, 0xe0, 0xbb, 0x5d, 0x55, 0xef,
|
||||
0x95, 0xb1, 0x6e, 0xcc, 0xc3, 0x6d, 0x55, 0xdd, 0xe2, 0xe8, 0x81, 0xda,
|
||||
0xaf, 0xcc, 0xbb, 0xc5, 0x47, 0x70, 0xb1, 0xc0, 0x85, 0x9f, 0xed, 0x70,
|
||||
0xd9, 0x72, 0x58, 0x55, 0xc5, 0x4e, 0x8b, 0x46, 0xde, 0x0e, 0x4b, 0xb1,
|
||||
0xb1, 0x9d, 0xd4, 0xb6, 0x12, 0xeb, 0x9a, 0xd4, 0xba, 0xad, 0x2c, 0xcd,
|
||||
0xb6, 0xf8, 0xbc, 0xb5, 0xad, 0xb4, 0x7a, 0x69, 0xe3, 0x66, 0x43, 0xb3,
|
||||
0x7c, 0x79, 0xa3, 0x0a, 0x24, 0xff, 0x54, 0x2c, 0x16, 0xce, 0x01, 0xee,
|
||||
0x17, 0xe7, 0x54, 0x7e, 0x18, 0xfa, 0x57, 0xac, 0xb0, 0x77, 0x7f, 0x1c,
|
||||
0x43, 0x71, 0x5f, 0x27, 0xd9, 0x1d, 0x3c, 0x45, 0xbf, 0x09, 0x69, 0x41,
|
||||
0xef, 0xa1, 0x75, 0x24, 0x38, 0x06, 0xe2, 0xd3, 0x7f, 0xa5, 0x38, 0x1c,
|
||||
0x00, 0x5e, 0xf2, 0x77, 0x24, 0xe6, 0x00, 0xfc, 0x95, 0x14, 0xf8, 0xf4,
|
||||
0xdf, 0xdc, 0x1e, 0x00, 0xe5, 0x18, 0x80, 0x3b, 0xc0, 0x30, 0x88, 0x29,
|
||||
0x90, 0xbf, 0x52, 0xc8, 0x37, 0x69, 0x70, 0xa7, 0xdb, 0x21, 0x69, 0xe7,
|
||||
0x40, 0x37, 0x64, 0x2f, 0x64, 0x44, 0x67, 0x6e, 0xa4, 0x13, 0x3a, 0x8b,
|
||||
0xc6, 0x4a, 0x8d, 0x25, 0xd0, 0x42, 0x98, 0x7a, 0x78, 0x74, 0x67, 0xb1,
|
||||
0x14, 0x43, 0x13, 0x4f, 0xdc, 0xa4, 0x3b, 0xc7, 0x29, 0xb2, 0x87, 0x7f,
|
||||
0xcd, 0xee, 0xa5, 0x29, 0x72, 0x1b, 0xf7, 0x43, 0xe7, 0xfd, 0xc9, 0xe6,
|
||||
0x77, 0xc5, 0x81, 0xfc, 0x91, 0xdf, 0xc4, 0xb8, 0xa2, 0x2f, 0xf9, 0x48,
|
||||
0x52, 0x0d, 0x1b, 0xea, 0xaf, 0x73, 0x1b, 0x19, 0xc6, 0x28, 0x08, 0x45,
|
||||
0xb5, 0xce, 0xfe, 0x3f, 0x41, 0x99, 0x72, 0xa3, 0x9d, 0x17, 0x72, 0x19,
|
||||
0xbd, 0x6a, 0xf6, 0xf8, 0x25, 0x68, 0xd3, 0x50, 0x2a, 0xf7, 0x01, 0x78,
|
||||
0x90, 0xb4, 0xee, 0x77, 0xcb, 0x26, 0x38, 0x89, 0x7d, 0x68, 0x93, 0xd8,
|
||||
0x7f, 0x06, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x08, 0x9c, 0x44, 0x70,
|
||||
0x17, 0x0f, 0x00, 0x00,
|
||||
},
|
||||
"assets/index.html",
|
||||
)
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
if f, ok := _bindata[name]; ok {
|
||||
return f()
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() ([]byte, error){
|
||||
"assets/glauth.css": assets_glauth_css,
|
||||
"assets/glauth.js": assets_glauth_js,
|
||||
"assets/index.html": assets_index_html,
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/nmcclain/ldap"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type configHandler struct {
|
||||
cfg *config
|
||||
}
|
||||
|
||||
func newConfigHandler(cfg *config) Backend {
|
||||
handler := configHandler{
|
||||
cfg: cfg,
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
log.Debug("Bind request as %s from %s", bindDN, conn.RemoteAddr().String())
|
||||
stats_frontend.Add("bind_reqs", 1)
|
||||
|
||||
// parse the bindDN
|
||||
if !strings.HasSuffix(bindDN, baseDN) {
|
||||
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), ",")
|
||||
groupName := ""
|
||||
userName := ""
|
||||
if len(parts) == 1 {
|
||||
userName = strings.TrimPrefix(parts[0], "cn=")
|
||||
} else if len(parts) == 2 {
|
||||
userName = strings.TrimPrefix(parts[0], "cn=")
|
||||
groupName = strings.TrimPrefix(parts[1], "ou=")
|
||||
} else {
|
||||
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{}
|
||||
found := false
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.Name == userName {
|
||||
found = true
|
||||
user = u
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Warning(fmt.Sprintf("Bind Error: User %s not found.", user))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
// find the group
|
||||
group := configGroup{}
|
||||
found = false
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.Name == groupName {
|
||||
found = true
|
||||
group = g
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Warning(fmt.Sprintf("Bind Error: Group %s not found.", group))
|
||||
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))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// finally, validate user's pw
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(bindSimplePw))
|
||||
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
|
||||
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("Bind success as %s from %s", bindDN, conn.RemoteAddr().String())
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||
log.Debug("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 {
|
||||
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)
|
||||
}
|
||||
switch filterEntity {
|
||||
default:
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, searchReq.Filter)
|
||||
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})
|
||||
}
|
||||
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}})
|
||||
|
||||
if len(u.GivenName) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"givenName", []string{u.GivenName}})
|
||||
}
|
||||
|
||||
if len(u.SN) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"sn", []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)}})
|
||||
|
||||
if u.Disabled {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"inactive"}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"accountStatus", []string{"active"}})
|
||||
}
|
||||
|
||||
if len(u.Mail) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"mail", []string{u.Mail}})
|
||||
}
|
||||
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixAccount"}})
|
||||
|
||||
if len(u.LoginShell) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{u.LoginShell}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"loginShell", []string{"/bin/bash"}})
|
||||
}
|
||||
|
||||
if len(u.Homedir) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{u.Homedir}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []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(u.OtherGroups)})
|
||||
if len(u.SSHKeys) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{"sshPublicKey", 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})
|
||||
}
|
||||
}
|
||||
stats_frontend.Add("search_successes", 1)
|
||||
log.Debug("AP: Search OK: %s", searchReq.Filter)
|
||||
return ldap.ServerSearchResult{entries, []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) Close(boundDn string, conn net.Conn) error {
|
||||
stats_frontend.Add("closes", 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupMembers(gid int) []string {
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.PrimaryGroup == gid {
|
||||
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, 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)
|
||||
members[dn] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m := []string{}
|
||||
for k, _ := range members {
|
||||
m = append(m, k)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.PrimaryGroup == gid {
|
||||
members[u.Name] = true
|
||||
} else {
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
members[u.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m := []string{}
|
||||
for k, _ := range members {
|
||||
m = append(m, k)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupDNs(gids []int) []string {
|
||||
groups := make(map[string]bool)
|
||||
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)
|
||||
groups[dn] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
g := []string{}
|
||||
for k, _ := range groups {
|
||||
g = append(g, k)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupName(gid int) string {
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.UnixID == gid {
|
||||
return g.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,26 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/docopt/docopt-go"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
"gopkg.in/amz.v1/aws"
|
||||
"gopkg.in/amz.v1/s3"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"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"
|
||||
logging "github.com/op/go-logging"
|
||||
"gopkg.in/amz.v1/aws"
|
||||
"gopkg.in/amz.v1/s3"
|
||||
)
|
||||
|
||||
var version = "1.0.1"
|
||||
// Set with buildtime vars
|
||||
var LastGitTag string
|
||||
var BuildTime string
|
||||
var GitCommit string
|
||||
var GitClean string
|
||||
var GitBranch string
|
||||
var GitTagIsCommit string
|
||||
|
||||
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
|
||||
|
||||
@@ -33,141 +45,153 @@ 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
|
||||
}
|
||||
activeConfig = &config.Config{}
|
||||
)
|
||||
|
||||
// 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
|
||||
Cert string
|
||||
Key string
|
||||
Listen string
|
||||
TLS bool
|
||||
}
|
||||
type configAPI struct {
|
||||
Cert string
|
||||
Enabled bool
|
||||
Key string
|
||||
Listen string
|
||||
SecretToken string
|
||||
TLS bool
|
||||
}
|
||||
type configUser struct {
|
||||
Name string
|
||||
OtherGroups []int
|
||||
PassSHA256 string
|
||||
PrimaryGroup int
|
||||
SSHKeys []string
|
||||
Disabled bool
|
||||
UnixID int
|
||||
Mail string
|
||||
LoginShell string
|
||||
GivenName string
|
||||
SN string
|
||||
Homedir string
|
||||
}
|
||||
type configGroup struct {
|
||||
Name string
|
||||
UnixID int
|
||||
}
|
||||
type config struct {
|
||||
API configAPI
|
||||
Backend configBackend
|
||||
Debug bool
|
||||
Frontend configFrontend
|
||||
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.
|
||||
func getVersionString() string {
|
||||
|
||||
var log = logging.MustGetLogger(programName)
|
||||
var versionstr string
|
||||
|
||||
versionstr = "GLauth"
|
||||
|
||||
// Notate the git context of the build
|
||||
switch {
|
||||
// If a release, use the tag
|
||||
case GitClean == "1" && GitTagIsCommit == "1":
|
||||
versionstr += " " + LastGitTag + "\n\n"
|
||||
|
||||
// If this branch had a tag before, mention the branch and the tag to give a rough idea of the base version
|
||||
case len(GitBranch) > 1 && len(LastGitTag) > 1:
|
||||
versionstr += "\nNon-release build from branch " + GitBranch + ", based on tag " + LastGitTag + "\n\n"
|
||||
|
||||
// If no previous tag specified, just mention the branch
|
||||
case len(GitBranch) > 1:
|
||||
versionstr += "\nNon-release build from branch " + GitBranch + "\n\n"
|
||||
|
||||
// Fallback message, if all else fails
|
||||
default:
|
||||
versionstr += "\nNon-release build\n\n"
|
||||
}
|
||||
|
||||
// Include build time
|
||||
if len(BuildTime) > 1 {
|
||||
versionstr += "Build time: " + BuildTime + "\n"
|
||||
}
|
||||
|
||||
// Add commit hash
|
||||
if GitClean == "1" && len(GitCommit) > 1 {
|
||||
versionstr += "Commit: " + GitCommit + "\n"
|
||||
}
|
||||
|
||||
return versionstr
|
||||
|
||||
}
|
||||
|
||||
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(version))
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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)
|
||||
startConfigWatcher()
|
||||
|
||||
// start the frontend server
|
||||
if cfg.Frontend.TLS {
|
||||
log.Notice(fmt.Sprintf("Frontend LDAPS server listening on %s", cfg.Frontend.Listen))
|
||||
if err := s.ListenAndServeTLS(cfg.Frontend.Listen, cfg.Frontend.Cert, cfg.Frontend.Key); err != nil {
|
||||
log.Fatalf("LDAP Server Failed: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Notice(fmt.Sprintf("Frontend LDAP server listening on %s", cfg.Frontend.Listen))
|
||||
if err := s.ListenAndServe(cfg.Frontend.Listen); err != nil {
|
||||
log.Fatalf("LDAP Server Failed: %s", err.Error())
|
||||
}
|
||||
s, err := server.NewServer(log, activeConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start server: %s", err.Error())
|
||||
}
|
||||
s.ListenAndServe()
|
||||
|
||||
log.Critical("AP exit")
|
||||
}
|
||||
|
||||
// doConfig reads the cli flags and config file
|
||||
func doConfig() (*config, error) {
|
||||
cfg := config{}
|
||||
// setup defaults
|
||||
cfg.Frontend.TLS = true
|
||||
func startConfigWatcher() {
|
||||
configFileLocation := getConfigLocation()
|
||||
|
||||
// parse the command-line args
|
||||
args, err := docopt.Parse(usage, nil, true, version, false)
|
||||
if err != nil {
|
||||
return &cfg, err
|
||||
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 parseArgs() error {
|
||||
var err error
|
||||
|
||||
if args, err = docopt.Parse(usage, nil, true, getVersionString(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
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"])
|
||||
}
|
||||
@@ -183,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)
|
||||
@@ -197,30 +221,129 @@ 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])")
|
||||
}
|
||||
|
||||
if len(cfg.Frontend.Listen) > 0 {
|
||||
// We're going with old format - parse it into new
|
||||
log.Warning("Config [frontend] is deprecated - please move to [ldap] and [ldaps] as-per documentation")
|
||||
|
||||
cfg.LDAP.Enabled = !cfg.Frontend.TLS
|
||||
cfg.LDAPS.Enabled = cfg.Frontend.TLS
|
||||
|
||||
if cfg.Frontend.TLS {
|
||||
cfg.LDAPS.Listen = cfg.Frontend.Listen
|
||||
} else {
|
||||
cfg.LDAP.Listen = cfg.Frontend.Listen
|
||||
}
|
||||
|
||||
if len(cfg.Frontend.Cert) > 0 {
|
||||
cfg.LDAPS.Cert = cfg.Frontend.Cert
|
||||
}
|
||||
if len(cfg.Frontend.Key) > 0 {
|
||||
cfg.LDAPS.Key = cfg.Frontend.Key
|
||||
}
|
||||
}
|
||||
|
||||
if !cfg.LDAP.Enabled && !cfg.LDAPS.Enabled {
|
||||
return &cfg, fmt.Errorf("No server configuration found: please provide either LDAP or LDAPS configuration")
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Enabled {
|
||||
// LDAPS enabled - verify requirements (cert, key, listen)
|
||||
if len(cfg.LDAPS.Cert) == 0 || len(cfg.LDAPS.Key) == 0 {
|
||||
return &cfg, fmt.Errorf("LDAPS was enabled but no certificate or key were specified: please disable LDAPS or use the 'cert' and 'key' options")
|
||||
}
|
||||
|
||||
if len(cfg.LDAPS.Listen) == 0 {
|
||||
return &cfg, fmt.Errorf("No LDAPS bind address was specified: please disable LDAPS or use the 'listen' option")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.LDAP.Enabled {
|
||||
// LDAP enabled - verify listen
|
||||
if len(cfg.LDAP.Listen) == 0 {
|
||||
return &cfg, fmt.Errorf("No LDAP bind address was specified: please disable LDAP or use the 'listen' option")
|
||||
}
|
||||
}
|
||||
|
||||
switch cfg.Backend.Datastore {
|
||||
case "":
|
||||
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))
|
||||
@@ -235,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,32 @@
|
||||
package main
|
||||
|
||||
// Just the beginning of testing - more to be added
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetVersionString(t *testing.T) {
|
||||
|
||||
// Setup config and check output
|
||||
LastGitTag = "7"
|
||||
BuildTime = "20180616_042045Z"
|
||||
GitCommit = "a9837f3112fc049fd1e9faca6f56d8be1bec91bd"
|
||||
GitClean = "1"
|
||||
GitBranch = "dev"
|
||||
GitTagIsCommit = "0"
|
||||
|
||||
versionOutput := getVersionString()
|
||||
if versionOutput != "7" {
|
||||
t.Errorf("Finish writing this test, output was: %s", versionOutput)
|
||||
}
|
||||
|
||||
// TODO: write these cases
|
||||
|
||||
// Cases to test:
|
||||
// 1) if GitTagIsCommit=1 - check for release-based strings (this build is release)
|
||||
// 2) if GitClean=1 - check for "Commit: " as well as value of commit str
|
||||
// 3) if GitClean=0 - Ensure "Commit: " is NOT there, as well ensure value of commit str is not there
|
||||
// 4) do additional checks for release behavior, as well as branch name
|
||||
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -0,0 +1,401 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"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 {
|
||||
log *logging.Logger
|
||||
cfg *config.Config
|
||||
yubikeyAuth *yubigo.YubiAuth
|
||||
}
|
||||
|
||||
func NewConfigHandler(log *logging.Logger, cfg *config.Config, yubikeyAuth *yubigo.YubiAuth) Handler {
|
||||
handler := configHandler{
|
||||
log: log,
|
||||
cfg: cfg,
|
||||
yubikeyAuth: yubikeyAuth}
|
||||
return handler
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
|
||||
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
|
||||
|
||||
stats.Frontend.Add("bind_reqs", 1)
|
||||
|
||||
// parse the bindDN - ensure that the bindDN ends with the BaseDN
|
||||
if !strings.HasSuffix(bindDN, baseDN) {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
|
||||
// h.log.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], h.cfg.Backend.NameFormat+"=")
|
||||
} else if len(parts) == 2 {
|
||||
userName = strings.TrimPrefix(parts[0], h.cfg.Backend.NameFormat+"=")
|
||||
groupName = strings.TrimPrefix(parts[1], h.cfg.Backend.GroupFormat+"=")
|
||||
} else {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// find the user
|
||||
user := config.User{}
|
||||
found := false
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.Name == userName {
|
||||
found = true
|
||||
user = u
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: User %s not found.", userName))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
// find the group
|
||||
group := config.Group{}
|
||||
found = false
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.Name == groupName {
|
||||
found = true
|
||||
group = g
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: Group %s not found.", groupName))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
// validate group membership
|
||||
if user.PrimaryGroup != group.UnixID {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: User %s primary group is not %s.", userName, groupName))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
validotp := false
|
||||
|
||||
if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {
|
||||
validotp = true
|
||||
}
|
||||
|
||||
if len(user.Yubikey) > 0 && h.yubikeyAuth != nil {
|
||||
if len(bindSimplePw) > 44 {
|
||||
otp := bindSimplePw[len(bindSimplePw)-44:]
|
||||
yubikeyid := otp[0:12]
|
||||
bindSimplePw = bindSimplePw[:len(bindSimplePw)-44]
|
||||
|
||||
if user.Yubikey == yubikeyid {
|
||||
_, ok, _ := h.yubikeyAuth.Verify(otp)
|
||||
|
||||
if ok {
|
||||
validotp = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the full bind password provided before possibly modifying
|
||||
// in the otp check
|
||||
// Generate a hash of the provided password
|
||||
hashFull := sha256.New()
|
||||
hashFull.Write([]byte(bindSimplePw))
|
||||
|
||||
// Test OTP if exists
|
||||
if len(user.OTPSecret) > 0 && !validotp {
|
||||
if len(bindSimplePw) > 6 {
|
||||
otp := bindSimplePw[len(bindSimplePw)-6:]
|
||||
bindSimplePw = bindSimplePw[:len(bindSimplePw)-6]
|
||||
|
||||
validotp = totp.Validate(otp, user.OTPSecret)
|
||||
}
|
||||
}
|
||||
|
||||
// finally, validate user's pw
|
||||
|
||||
// check app passwords first
|
||||
for index, appPw := range user.PassAppSHA256 {
|
||||
|
||||
if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
|
||||
h.log.Debug(fmt.Sprintf("Attempted to bind app pw #%d - failure as %s from %s", index, bindDN, conn.RemoteAddr().String()))
|
||||
} else {
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.log.Debug("Bind success using app pw #%d as %s from %s", index, bindDN, conn.RemoteAddr().String())
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// then check main password with the hash
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(bindSimplePw))
|
||||
|
||||
// Then ensure the OTP is valid before checking
|
||||
if !validotp {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// Now, check the hash
|
||||
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: invalid credentials as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter))
|
||||
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)
|
||||
}
|
||||
switch filterEntity {
|
||||
default:
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, searchReq.Filter)
|
||||
case "posixgroup":
|
||||
for _, g := range h.cfg.Groups {
|
||||
attrs := []*ldap.EntryAttribute{}
|
||||
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{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{Name: "givenName", Values: []string{u.GivenName}})
|
||||
}
|
||||
|
||||
if len(u.SN) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "sn", Values: []string{u.SN}})
|
||||
}
|
||||
|
||||
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{Name: "accountStatus", Values: []string{"inactive"}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
|
||||
}
|
||||
|
||||
if len(u.Mail) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "mail", Values: []string{u.Mail}})
|
||||
}
|
||||
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixAccount", "shadowAccount"}})
|
||||
|
||||
if len(u.LoginShell) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{u.LoginShell}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "loginShell", Values: []string{"/bin/bash"}})
|
||||
}
|
||||
|
||||
if len(u.Homedir) > 0 {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{u.Homedir}})
|
||||
} else {
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "homeDirectory", Values: []string{"/home/" + u.Name}})
|
||||
}
|
||||
|
||||
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{Name: h.cfg.Backend.SSHKeyAttr, Values: u.SSHKeys})
|
||||
}
|
||||
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)
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
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("%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("%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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
if gid == g.UnixID {
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid != gid {
|
||||
includegroupmembers := h.getGroupMembers(includegroupid)
|
||||
|
||||
for _, includegroupmember := range includegroupmembers {
|
||||
members[includegroupmember] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m := []string{}
|
||||
for k, _ := range members {
|
||||
m = append(m, k)
|
||||
}
|
||||
|
||||
sort.Strings(m)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
if u.PrimaryGroup == gid {
|
||||
members[u.Name] = true
|
||||
} else {
|
||||
for _, othergid := range u.OtherGroups {
|
||||
if othergid == gid {
|
||||
members[u.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range h.cfg.Groups {
|
||||
if gid == g.UnixID {
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid == gid {
|
||||
h.log.Warning(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
|
||||
} else {
|
||||
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
|
||||
|
||||
for _, includegroupmemberid := range includegroupmemberids {
|
||||
members[includegroupmemberid] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m := []string{}
|
||||
for k, _ := range members {
|
||||
m = append(m, k)
|
||||
}
|
||||
|
||||
sort.Strings(m)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Converts an array of GUIDs into an array of DNs
|
||||
func (h configHandler) getGroupDNs(gids []int) []string {
|
||||
groups := make(map[string]bool)
|
||||
for _, gid := range gids {
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.UnixID == gid {
|
||||
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.cfg.Backend.GroupFormat, h.cfg.Backend.BaseDN)
|
||||
groups[dn] = true
|
||||
}
|
||||
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid == gid && g.UnixID != gid {
|
||||
includegroupdns := h.getGroupDNs([]int{g.UnixID})
|
||||
|
||||
for _, includegroupdn := range includegroupdns {
|
||||
groups[includegroupdn] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g := []string{}
|
||||
for k, _ := range groups {
|
||||
g = append(g, k)
|
||||
}
|
||||
|
||||
sort.Strings(g)
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupName(gid int) string {
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.UnixID == gid {
|
||||
return g.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -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("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("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("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("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("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("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("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("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("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("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("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("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
|
||||
+59
-10
@@ -4,21 +4,53 @@
|
||||
#################
|
||||
# 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
|
||||
|
||||
#################
|
||||
# The frontend section controls how clients connect to the proxy.
|
||||
[frontend]
|
||||
tls = false # enable TLS for production!!
|
||||
listen = "localhost:3893"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
# yubikeyclientid = "yubi-api-clientid"
|
||||
# yubikeysecret = "yubi-api-secret"
|
||||
|
||||
#################
|
||||
# Server configuration.
|
||||
#
|
||||
# This is the old server-config format
|
||||
# - will still work, but please use new format, shown below
|
||||
#
|
||||
# [frontend]
|
||||
# listen = "localhost:8484"
|
||||
# tls = false
|
||||
# cert ="certs/server.crt"
|
||||
# key = "certs/server.key"
|
||||
|
||||
# This ([ldap] and [ldaps]) is the new server-config format
|
||||
[ldap]
|
||||
enabled = true
|
||||
listen = "0.0.0.0:3893"
|
||||
|
||||
[ldaps]
|
||||
enabled = true
|
||||
listen = "0.0.0.0:3894"
|
||||
cert = "certs/server.crt"
|
||||
key = "certs/server.key"
|
||||
|
||||
#################
|
||||
# The backend section controls the data store.
|
||||
[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.
|
||||
@@ -41,7 +73,11 @@ debug = true
|
||||
homeDir = "/root"
|
||||
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
|
||||
sshkeys = ["ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwVw3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4hC/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLVIfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XBYnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+txSuhw== rsa-key-20160209"]
|
||||
|
||||
passappsha256 = [
|
||||
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
|
||||
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
|
||||
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
|
||||
]
|
||||
|
||||
[[users]]
|
||||
name = "serviceuser"
|
||||
@@ -49,6 +85,15 @@ debug = true
|
||||
primarygroup = 5502
|
||||
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
|
||||
|
||||
# Test user showing 2 factor auth authentication
|
||||
[[users]]
|
||||
name = "otpuser"
|
||||
unixid = 5004
|
||||
primarygroup = 5501
|
||||
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
|
||||
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
|
||||
yubikey = "vvjrcfalhlaa"
|
||||
|
||||
#################
|
||||
# The groups section contains a hardcoded list of valid users.
|
||||
[[groups]]
|
||||
@@ -59,12 +104,16 @@ debug = true
|
||||
name = "svcaccts"
|
||||
unixid = 5502
|
||||
|
||||
[[groups]]
|
||||
name = "vpn"
|
||||
unixid = 5503
|
||||
includegroups = [ 5501 ]
|
||||
|
||||
#################
|
||||
# Enable and configure the optional REST API here.
|
||||
[api]
|
||||
enabled = true
|
||||
tls = false # enable TLS for production!!
|
||||
listen = "localhost:5555"
|
||||
listen = "0.0.0.0:5555"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ debug = true
|
||||
# The frontend section controls how clients connect to the proxy.
|
||||
[frontend]
|
||||
tls = false # enable TLS for production!!
|
||||
listen = "localhost:3893"
|
||||
listen = "localhost:389"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
|
||||
|
||||
+20
-1
@@ -2,8 +2,27 @@
|
||||
|
||||
# Copy config file if it doesn't exist so that the app can start
|
||||
if [ ! -f /app/config/config.cfg ] ; then
|
||||
cp /app/docker/default-config.cfg /app/config/config.cfg;
|
||||
echo "Config file not found at /app/config/config.cfg"
|
||||
echo "Copying example configuration file to run."
|
||||
cp /app/docker/default-config.cfg /app/config/config.cfg || exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo "";
|
||||
echo "Version and build information:";
|
||||
echo "";
|
||||
|
||||
# Output version string to logs
|
||||
/app/glauth --version
|
||||
|
||||
|
||||
echo "";
|
||||
echo "Starting GLauth now.";
|
||||
echo "";
|
||||
|
||||
# Run app
|
||||
/app/glauth -c /app/config/config.cfg
|
||||
|
||||
echo ""
|
||||
echo "GLauth has exited."
|
||||
echo "Exiting."
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/test-results
|
||||
@@ -0,0 +1,202 @@
|
||||
dn: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
cn: hackers
|
||||
uid: hackers
|
||||
ou: superheros
|
||||
uidNumber: 5001
|
||||
accountStatus: active
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/hackers
|
||||
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
|
||||
uid: johndoe
|
||||
givenName: John
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5002
|
||||
accountStatus: active
|
||||
mail: jdoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /root
|
||||
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
|
||||
IfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XB
|
||||
YnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+
|
||||
txSuhw== rsa-key-20160209
|
||||
|
||||
dn: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: jamesdoe
|
||||
uid: jamesdoe
|
||||
givenName: James
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5003
|
||||
accountStatus: active
|
||||
mail: jdoe2@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/jamesdoe
|
||||
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
|
||||
IfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XB
|
||||
YnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+
|
||||
txSuhw== rsa-key-20160209
|
||||
|
||||
dn: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: alexdoe
|
||||
uid: alexdoe
|
||||
givenName: Alex
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5004
|
||||
accountStatus: active
|
||||
mail: adoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/alexdoe
|
||||
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
|
||||
uid: jackdoe
|
||||
givenName: Jack
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5005
|
||||
accountStatus: active
|
||||
mail: jdoe3@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/jackdoe
|
||||
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
|
||||
uid: sarahdoe
|
||||
givenName: Sarah
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5006
|
||||
accountStatus: active
|
||||
mail: sdoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/sarahdoe
|
||||
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
|
||||
uid: serviceuser
|
||||
ou: svcaccts
|
||||
uidNumber: 5003
|
||||
accountStatus: active
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/serviceuser
|
||||
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
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
dn: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
cn: superheros
|
||||
uid: superheros
|
||||
description: superheros
|
||||
gidNumber: 5501
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
|
||||
dn: cn=svcaccts,ou=groups,dc=glauth,dc=com
|
||||
cn: svcaccts
|
||||
uid: svcaccts
|
||||
description: svcaccts
|
||||
gidNumber: 5502
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
memberUid: serviceuser
|
||||
|
||||
dn: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
cn: vpnaccess
|
||||
uid: vpnaccess
|
||||
description: vpnaccess
|
||||
gidNumber: 5503
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
|
||||
dn: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
cn: allaccs
|
||||
uid: allaccs
|
||||
description: allaccs
|
||||
gidNumber: 5504
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: hackers
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: johndoe
|
||||
memberUid: sarahdoe
|
||||
memberUid: serviceuser
|
||||
|
||||
dn: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
cn: mailadmin
|
||||
uid: mailadmin
|
||||
description: mailadmin
|
||||
gidNumber: 5505
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: sarahdoe
|
||||
|
||||
dn: cn=webmail,ou=groups,dc=glauth,dc=com
|
||||
cn: webmail
|
||||
uid: webmail
|
||||
description: webmail
|
||||
gidNumber: 5506
|
||||
objectClass: posixGroup
|
||||
|
||||
dn: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
cn: fulltime
|
||||
uid: fulltime
|
||||
description: fulltime
|
||||
gidNumber: 5507
|
||||
objectClass: posixGroup
|
||||
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
memberUid: alexdoe
|
||||
memberUid: jackdoe
|
||||
memberUid: jamesdoe
|
||||
memberUid: sarahdoe
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
dn: cn=hackers,ou=superheros,dc=glauth,dc=com
|
||||
cn: hackers
|
||||
uid: hackers
|
||||
ou: superheros
|
||||
uidNumber: 5001
|
||||
accountStatus: active
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/hackers
|
||||
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
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
dn: cn=johndoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: johndoe
|
||||
uid: johndoe
|
||||
givenName: John
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5002
|
||||
accountStatus: active
|
||||
mail: jdoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /root
|
||||
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
|
||||
IfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XB
|
||||
YnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+
|
||||
txSuhw== rsa-key-20160209
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
dn: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
cn: serviceuser
|
||||
uid: serviceuser
|
||||
ou: svcaccts
|
||||
uidNumber: 5003
|
||||
accountStatus: active
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/bash
|
||||
homeDirectory: /home/serviceuser
|
||||
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
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
dn: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: jamesdoe
|
||||
uid: jamesdoe
|
||||
givenName: James
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5003
|
||||
accountStatus: active
|
||||
mail: jdoe2@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/jamesdoe
|
||||
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
|
||||
IfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XB
|
||||
YnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+
|
||||
txSuhw== rsa-key-20160209
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
dn: cn=alexdoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: alexdoe
|
||||
uid: alexdoe
|
||||
givenName: Alex
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5004
|
||||
accountStatus: active
|
||||
mail: adoe@example.com
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/alexdoe
|
||||
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
|
||||
|
||||
@@ -1,11 +1,257 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo apt-get -qq update && sudo apt-get -qq install -y ldap-utils &&
|
||||
export CLEANUP="$1"
|
||||
|
||||
$TRAVIS_BUILD_DIR/glauth -c "$TRAVIS_BUILD_DIR/$1" &
|
||||
## Main Methods
|
||||
|
||||
|
||||
# Dep check
|
||||
|
||||
command -v oathtool ;
|
||||
if [[ "$?" = "1" ]] ; then
|
||||
echo "Please install oathtool or add it to \$PATH before continuing."
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
# Get the git working directory base if travis build dir isn't set
|
||||
if [[ "$TRAVIS_BUILD_DIR" == "" ]] ; then
|
||||
export TRAVIS_BUILD_DIR="$(git rev-parse --show-toplevel)"
|
||||
fi
|
||||
|
||||
# This script requires that "$TRAVIS_BUILD_DIR" is set to the repo root
|
||||
|
||||
# Ensure ldap utils are installed (for example - when running this outside of travis)
|
||||
if [[ ! `which ldapsearch` ]]; then
|
||||
sudo apt-get -qq update && sudo apt-get -qq install -y ldap-utils || exit 1;
|
||||
fi
|
||||
|
||||
# Display version string
|
||||
echo "";
|
||||
echo ""
|
||||
echo "Version string of tested binary:"
|
||||
"$TRAVIS_BUILD_DIR/bin/glauth64" --version
|
||||
echo ""
|
||||
|
||||
# Start in background, capture PID
|
||||
"$TRAVIS_BUILD_DIR/bin/glauth64" -c "$TRAVIS_BUILD_DIR/scripts/travis/test-config.cfg" &> /dev/null &
|
||||
|
||||
# Use this instead to see glauth logs while running
|
||||
# "$TRAVIS_BUILD_DIR/bin/glauth64" -c "$TRAVIS_BUILD_DIR/scripts/travis/test-config.cfg" &
|
||||
glauthPid="$!"
|
||||
|
||||
echo "Running at $glauthPid"
|
||||
echo "Running glauth at PID=$glauthPid"
|
||||
|
||||
# Sleep 2 sec, to ensure it comes online successfully and stays up
|
||||
sleep 2;
|
||||
kill $glauthPid;
|
||||
echo "done."
|
||||
|
||||
# Check if process is still running before continuing
|
||||
ps aux | grep -v "grep" | grep "$glauthPid" &> /dev/null || FAIL="1"
|
||||
|
||||
if [[ "$FAIL" = "1" ]] ; then
|
||||
echo "Integration test FAILED - process did not remain running > 2 sec"
|
||||
exit 255;
|
||||
fi
|
||||
|
||||
|
||||
FAIL="0"
|
||||
|
||||
# Used for OTP testing
|
||||
# Arguments:
|
||||
# $1 - Full Bind DN
|
||||
# $2 - Full Bind PW
|
||||
# $3 - query to run
|
||||
# $4 - test name
|
||||
function bindTest() {
|
||||
ldapsearch -LLL \
|
||||
-H ldap://localhost:3893 \
|
||||
-D "$1" \
|
||||
-w "$2" \
|
||||
-x -bdc=glauth,dc=com \
|
||||
"$3" > /dev/null
|
||||
|
||||
exitCode="$?"
|
||||
|
||||
if [[ "$exitCode" = "0" ]] ; then
|
||||
echo " - PASS : Bind test '$4'";
|
||||
else
|
||||
echo " - FAIL : Bind test '$4'";
|
||||
FAIL="1"
|
||||
fi
|
||||
|
||||
|
||||
}
|
||||
|
||||
# Arguments:
|
||||
# $1 - query
|
||||
# $2 - name of snapshot
|
||||
function snapshotTest() {
|
||||
|
||||
goodResults="$TRAVIS_BUILD_DIR/scripts/travis/good-results"
|
||||
testResults="$TRAVIS_BUILD_DIR/scripts/travis/test-results"
|
||||
|
||||
mkdir "$testResults" &> /dev/null
|
||||
|
||||
# Run tests here
|
||||
ldapCmd="ldapsearch -LLL \
|
||||
-H ldap://localhost:3893 \
|
||||
-D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com \
|
||||
-w mysecret \
|
||||
-x -bdc=glauth,dc=com \
|
||||
$1";
|
||||
|
||||
# Run the ldap command, pipe to file
|
||||
# "$ldapCmd" &> "$testResults/$2"
|
||||
|
||||
# Useful for debugging - output the command run
|
||||
# echo "$ldapCmd";
|
||||
|
||||
ldapsearch -LLL \
|
||||
-H ldap://localhost:3893 \
|
||||
-D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com \
|
||||
-w mysecret \
|
||||
-x -bdc=glauth,dc=com \
|
||||
$1 > "$testResults/$2"
|
||||
|
||||
|
||||
# Handle the first-run case, saving the test results
|
||||
if [ ! -f "$goodResults/$2" ]; then
|
||||
echo " - FIRST RUN - SAVING SNAPSHOT, RUN AGAIN TO PASS : '$2'";
|
||||
|
||||
# Copy the results to the goodResults dir, for future runs.
|
||||
cp "$testResults/$2" "$goodResults/$2";
|
||||
|
||||
# NOTE: fail=1 must still be set, otherwise CI runs would succeed when they shouldn't
|
||||
FAIL="1"
|
||||
else
|
||||
|
||||
THISFAIL="0"
|
||||
diff -u "$goodResults/$2" "$testResults/$2" || THISFAIL="1"
|
||||
|
||||
|
||||
if [[ "$CLEANUP" = "cleanup" ]] ; then
|
||||
rm -rf "$testResults"
|
||||
fi
|
||||
|
||||
if [[ "$THISFAIL" = "0" ]] ; then
|
||||
echo " - PASS : snapshot '$2'";
|
||||
else
|
||||
echo " - FAIL : snapshot '$2'";
|
||||
FAIL="1"
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
echo "";
|
||||
echo "";
|
||||
echo "RUNNING TESTS:"
|
||||
echo "";
|
||||
|
||||
#################
|
||||
## TEST RUNS
|
||||
#################
|
||||
|
||||
## Query output tests
|
||||
|
||||
# Regular single-user fetches
|
||||
snapshotTest "cn=hackers" userFetchTest0
|
||||
snapshotTest "cn=johndoe" userFetchTest1
|
||||
snapshotTest "cn=serviceuser" userFetchTest2
|
||||
snapshotTest "cn=jamesdoe" userFetchTest3
|
||||
snapshotTest "cn=alexdoe" userFetchTest4
|
||||
|
||||
# Test result of fetching nonexistent users
|
||||
snapshotTest "cn=fakeuser" userFetchNonexistentUser0
|
||||
snapshotTest "cn=janedoe" userFetchNonexistentUser1
|
||||
|
||||
# List all posixgroups
|
||||
snapshotTest "objectClass=posixgroup" posixGroupList0
|
||||
snapshotTest "objectClass=posixaccount" posixAccountList0
|
||||
|
||||
|
||||
## 2FA Bind Test
|
||||
# Fetch the OTP for this moment
|
||||
otpCode="$(oathtool --totp -b -d 6 '3hnvnk4ycv44glzigd6s25j4dougs3rk')"
|
||||
|
||||
pass="ThisAloneWontWork!"
|
||||
|
||||
# Try to bind with it
|
||||
bindTest "cn=alexdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"$pass$otpCode" \
|
||||
"cn=alexdoe" \
|
||||
"OtpAlexDoe"
|
||||
|
||||
## App Password Bind Test
|
||||
|
||||
# Test the main pw
|
||||
bindTest "cn=jackdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"dogood1" \
|
||||
"cn=jackdoe" \
|
||||
"AppPwNoOtp0"
|
||||
|
||||
# App passwords on user
|
||||
bindTest "cn=jackdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw1" \
|
||||
"cn=jackdoe" \
|
||||
"AppPwNoOtp1"
|
||||
|
||||
bindTest "cn=jackdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw2" \
|
||||
"cn=jackdoe" \
|
||||
"AppPwNoOtp2"
|
||||
|
||||
bindTest "cn=jackdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw3" \
|
||||
"cn=jackdoe" \
|
||||
"AppPwNoOtp3"
|
||||
|
||||
|
||||
####
|
||||
# Test for a user who also uses OTP
|
||||
####
|
||||
otpCode="$(oathtool --totp -b -d 6 '3hnvnk4ycv44glzigd6s25j4dougs3rk')"
|
||||
|
||||
pass="dogood1"
|
||||
|
||||
# Test the main pw
|
||||
bindTest "cn=sarahdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"$pass$otpCode" \
|
||||
"cn=sarahdoe" \
|
||||
"AppPwOtp0"
|
||||
|
||||
# App passwords on user
|
||||
bindTest "cn=sarahdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw1" \
|
||||
"cn=sarahdoe" \
|
||||
"AppPwOtp1"
|
||||
|
||||
bindTest "cn=sarahdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw2" \
|
||||
"cn=sarahdoe" \
|
||||
"AppPwOtp2"
|
||||
|
||||
bindTest "cn=sarahdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw3" \
|
||||
"cn=sarahdoe" \
|
||||
"AppPwOtp3"
|
||||
|
||||
#############
|
||||
## Cleanup
|
||||
#############
|
||||
|
||||
echo "";
|
||||
echo "";
|
||||
|
||||
# Kill saved PID when done
|
||||
# However - throw the fail flag if the process isn't there (ie, exited prematurely)
|
||||
echo "Killing glauth"
|
||||
kill "$glauthPid" || FAIL="1"
|
||||
|
||||
if [[ "$FAIL" = "0" ]] ; then
|
||||
echo "Integration test success"
|
||||
exit 0;
|
||||
else
|
||||
echo "Integration test FAILED"
|
||||
exit 255;
|
||||
fi
|
||||
|
||||
@@ -10,6 +10,9 @@ debug = true
|
||||
# The frontend section controls how clients connect to the proxy.
|
||||
[frontend]
|
||||
tls = false # enable TLS for production!!
|
||||
|
||||
# Using a nonstandard port to ensure there are no
|
||||
# conflicts if running a test alongside a prod instance
|
||||
listen = "localhost:3893"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
@@ -42,6 +45,70 @@ debug = true
|
||||
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
|
||||
sshkeys = ["ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwVw3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4hC/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLVIfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XBYnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+txSuhw== rsa-key-20160209"]
|
||||
|
||||
# This user record tests lots of groups, including group inheritence
|
||||
[[users]]
|
||||
name = "jamesdoe"
|
||||
givenname="James"
|
||||
sn="Doe"
|
||||
mail = "jdoe2@example.com"
|
||||
unixid = 5003
|
||||
primarygroup = 5501
|
||||
loginShell = "/bin/sh"
|
||||
otherGroups = [5505,5507]
|
||||
homeDir = "/home/jamesdoe"
|
||||
passsha256 = "ff522a502031e5f37054892d642c2c0f36af6c4e8f62225470d4a1a99f3c9c2a" # dogood1
|
||||
sshkeys = ["ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwVw3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4hC/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLVIfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XBYnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+txSuhw== rsa-key-20160209"]
|
||||
|
||||
# 2FA test user
|
||||
[[users]]
|
||||
name = "alexdoe"
|
||||
givenname="Alex"
|
||||
sn="Doe"
|
||||
mail = "adoe@example.com"
|
||||
unixid = 5004
|
||||
primarygroup = 5501
|
||||
loginShell = "/bin/sh"
|
||||
otherGroups = [5505,5507]
|
||||
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
|
||||
passsha256 = "b1cae08a1fd8b0bd0855e48f74ba2f8548904dd5de72311e6234617a3f661e16" # ThisAloneWontWork!
|
||||
|
||||
|
||||
|
||||
# App PW test user without OTP
|
||||
[[users]]
|
||||
name = "jackdoe"
|
||||
givenname="Jack"
|
||||
sn="Doe"
|
||||
mail = "jdoe3@example.com"
|
||||
unixid = 5005
|
||||
primarygroup = 5501
|
||||
loginShell = "/bin/sh"
|
||||
otherGroups = [5505,5507]
|
||||
passsha256 = "ff522a502031e5f37054892d642c2c0f36af6c4e8f62225470d4a1a99f3c9c2a" #dogood1
|
||||
passappsha256 = [
|
||||
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
|
||||
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
|
||||
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
|
||||
]
|
||||
|
||||
# App PW test user with OTP
|
||||
[[users]]
|
||||
name = "sarahdoe"
|
||||
givenname="Sarah"
|
||||
sn="Doe"
|
||||
mail = "sdoe@example.com"
|
||||
unixid = 5006
|
||||
primarygroup = 5501
|
||||
loginShell = "/bin/sh"
|
||||
otherGroups = [5505,5507]
|
||||
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
|
||||
passsha256 = "ff522a502031e5f37054892d642c2c0f36af6c4e8f62225470d4a1a99f3c9c2a" #dogood1
|
||||
passappsha256 = [
|
||||
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
|
||||
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
|
||||
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
|
||||
]
|
||||
|
||||
|
||||
[[users]]
|
||||
name = "serviceuser"
|
||||
@@ -59,12 +126,35 @@ debug = true
|
||||
name = "svcaccts"
|
||||
unixid = 5502
|
||||
|
||||
|
||||
[[groups]]
|
||||
name = "vpnaccess"
|
||||
unixid = 5503
|
||||
includegroups = [ 5501 ]
|
||||
|
||||
[[groups]]
|
||||
name = "allaccs"
|
||||
unixid = 5504
|
||||
includegroups = [ 5501, 5502 ]
|
||||
|
||||
[[groups]]
|
||||
name = "mailadmin"
|
||||
unixid = 5505
|
||||
|
||||
[[groups]]
|
||||
name = "webmail"
|
||||
unixid = 5506
|
||||
|
||||
|
||||
[[groups]]
|
||||
name = "fulltime"
|
||||
unixid = 5507
|
||||
|
||||
#################
|
||||
# Enable and configure the optional REST API here.
|
||||
[api]
|
||||
enabled = true
|
||||
enabled = false
|
||||
tls = false # enable TLS for production!!
|
||||
listen = "localhost:5555"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user