Compare commits

..

109 Commits

Author SHA1 Message Date
Jörn Friedrich Dreyer 1b92cfbae6 v2.0.0 RC1 (#125)
Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Zhou Wenzong <wenzong@users.noreply.github.com>
Co-authored-by: Jairo Llopis <Yajo@users.noreply.github.com>
Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: Nicolai Willems <172633+nwillems@users.noreply.github.com>
Co-authored-by: Thibault Soubiran <soubi.thibault@gmail.com>
Co-authored-by: Clement JACOB <clems71@gmail.com>
Co-authored-by: Lutz Horn <code@lhorn.de>
Co-authored-by: Márk Sági-Kazár <sagikazarmark@users.noreply.github.com>
Co-authored-by: Peter Heise <pheise@koprolalie.com>
Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: Chris F Ravenscroft <chris@voilaweb.com>
Co-authored-by: fanlix <fanlix@gmail.com>
2021-08-12 21:41:58 -07:00
Ben Yanke 5681dc4d02 Remove byanke's donation fields 2021-02-27 07:51:46 -06:00
Jörn Friedrich Dreyer a92f9a7028 remove unsupported deploy option: body (#114)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-02-24 16:03:07 +01:00
Jörn Friedrich Dreyer 182d7c0496 Use travis/dpv v1 body option for release message (#113)
dpl v2 is not yet used in production for travis, so I ran into https://travis-ci.org/glauth/glauth/jobs/654427357#L556

v1 directly uses octokits parameters: https://github.com/octokit/octokit.rb/blob/master/lib/octokit/client/releases.rb#L35
2020-02-24 15:40:51 +01:00
Jörn Friedrich Dreyer 6278bc34d4 fix release message escaping (#112)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-02-24 15:12:27 +01:00
Jörn Friedrich Dreyer 78a98d75a9 use bot for travis (#111)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-02-24 14:01:34 +01:00
Jörn Friedrich Dreyer 6928b3b608 Merge branch 'dev' 2020-02-24 12:16:02 +01:00
Jörn Friedrich Dreyer ac16a07a63 Merge branch 'master' into dev 2020-02-24 12:09:13 +01:00
Jörn Friedrich Dreyer 363336bf94 update travis deploy condition
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-02-24 11:47:48 +01:00
Jörn Friedrich Dreyer 07644bd957 Update dev (#109)
* Pulling in latest changes and fixes for  1.1.1 (#56)

* Adding a few test dockerfiles

* Continued work on docker builds

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

* Continuing to work on docker build

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

* Implementing dumb init

* Docker build fully switched to alpine

* Setting up alpine build

* Cleaning up dockerfile

* Cleaning up repo in prep for merge

* Updating permissions for clean merge

* Removing old ansible cruft, as per #12

* Removing binaries (since we now build)

* Updating sample config to use new fields now available

* Testing travis

* another travis test

* Still working on travis builds

* Fix missing amazon packages

* Merge in Travis config feature branch (#26)

* Adding version string

* Fixing and cleaning up docker build

* Adding image hashing and verification to 'make all'

* Testing build

* Running gofmt to fix build

* Testing switching travis to make

* Testing build again

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

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

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

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

* Remove needless comment from integration test

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

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

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

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

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

* Forgot to remove spare test

* Add Version Info at Buildtime  (#39)

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

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

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

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

Build time: 20180601_041330Z
Commit: 931d666262

```

* Fixing Broken Docker Build (#41)

Forgot to include go-bindata run in Dockerfile.

* Removing leftover TODO from merge

* Testing releases

* Testing releases

* Testing releases

* Adjusting logging

* Add new group tests to integration tests

* add cleanup to test process

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

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

* Syntax fix

* starting framework for unit tests

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

Fixes a few minor issues:

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

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

* Forgot to run 'go fmt'

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

* Testing not quite ready yet, removing from build

* Add Support For 2 Factor Authentication

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

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

* Expose LDAPS ports in Docker container (#49)

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

* Adding better logging to docker start script

* Add more logging info to docker startup script

* Fix Arm32 Build (#52)

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

fixes #52

* Update docker hub image badge to a working one

previous one simply returned 0.

* Removing 3893 - fixes #50

* Fixing readme MD formatting

* Adding travis_retry to fix against intermittent network outages

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

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

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

* Adding PR template and improving integration test tooling

* Updating formatting

* Add get dependencies step to makefile setup

* Add go 1.11

* Add App Password Support (#60)

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

Fixes #54

* Adding NCoC as official project code of conduct

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

* Properly handling paramaters in logs - fixes #64

* Adding ldapsearch for healthchecks

* fixed quoting in docker startup script (#77)

Thanks @cxcv cxcv

* Minor tweak of help text

* More durable Dockerfile (#92)

Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: Andrea Cervesato <andrea.cervesato@gmail.com>
2020-02-21 21:26:13 +01:00
Jörn Friedrich Dreyer 558c7de15a Refactor packages (#105)
* Pulling in latest changes and fixes for  1.1.1 (#56)

* Adding a few test dockerfiles

* Continued work on docker builds

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

* Continuing to work on docker build

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

* Implementing dumb init

* Docker build fully switched to alpine

* Setting up alpine build

* Cleaning up dockerfile

* Cleaning up repo in prep for merge

* Updating permissions for clean merge

* Removing old ansible cruft, as per #12

* Removing binaries (since we now build)

* Updating sample config to use new fields now available

* Testing travis

* another travis test

* Still working on travis builds

* Fix missing amazon packages

* Merge in Travis config feature branch (#26)

* Adding version string

* Fixing and cleaning up docker build

* Adding image hashing and verification to 'make all'

* Testing build

* Running gofmt to fix build

* Testing switching travis to make

* Testing build again

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

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

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

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

* Remove needless comment from integration test

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

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

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

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

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

* Forgot to remove spare test

* Add Version Info at Buildtime  (#39)

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

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

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

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

Build time: 20180601_041330Z
Commit: 931d666262

```

* Fixing Broken Docker Build (#41)

Forgot to include go-bindata run in Dockerfile.

* Removing leftover TODO from merge

* Testing releases

* Testing releases

* Testing releases

* Adjusting logging

* Add new group tests to integration tests

* add cleanup to test process

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

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

* Syntax fix

* starting framework for unit tests

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

Fixes a few minor issues:

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

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

* Forgot to run 'go fmt'

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

* Testing not quite ready yet, removing from build

* Add Support For 2 Factor Authentication

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

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

* Expose LDAPS ports in Docker container (#49)

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

* Adding better logging to docker start script

* Add more logging info to docker startup script

* Fix Arm32 Build (#52)

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

fixes #52

* Update docker hub image badge to a working one

previous one simply returned 0.

* Removing 3893 - fixes #50

* Fixing readme MD formatting

* Adding travis_retry to fix against intermittent network outages

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

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

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

* Adding PR template and improving integration test tooling

* Updating formatting

* Add get dependencies step to makefile setup

* Add go 1.11

* Add App Password Support (#60)

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

Fixes #54

* Adding NCoC as official project code of conduct

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

* Properly handling paramaters in logs - fixes #64

* Adding ldapsearch for healthchecks

* fixed quoting in docker startup script (#77)

Thanks @cxcv cxcv

* Minor tweak of help text

* More durable Dockerfile (#92)

* refactor with latest dev

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

* fix modules, update .gitignore

* pass in logger

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

Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: Andrea Cervesato <andrea.cervesato@gmail.com>
2020-02-21 17:10:41 +01:00
Thomas Müller 9b90466286 Feature/owncloud10 backend graphapi (#104)
* Add ownCloud 10 backend supporting provisioning api as well as graph api

* Use github.com/DeepDiver1975/msgraph.go/v1.0

* List members in groups when using graph api

* Handle searchBaseDN pointing to a user
2020-02-21 11:26:33 +01:00
Dan Anderson 3445f388b0 add "trimpath" to build arguments to remove build path from released binaries (#67)
Co-authored-by: dankn0x <60980300+dankn0x@users.noreply.github.com>
Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-02-21 09:34:37 +01:00
Thomas Müller 14139e878d travis-ci: run with go 1.11, 1.12 and 1.13 (#102)
Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-02-21 09:22:38 +01:00
Jörn Friedrich Dreyer b6c6730cfd Update dev (#101)
* Pulling in latest changes and fixes for  1.1.1 (#56)

* Adding a few test dockerfiles

* Continued work on docker builds

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

* Continuing to work on docker build

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

* Implementing dumb init

* Docker build fully switched to alpine

* Setting up alpine build

* Cleaning up dockerfile

* Cleaning up repo in prep for merge

* Updating permissions for clean merge

* Removing old ansible cruft, as per #12

* Removing binaries (since we now build)

* Updating sample config to use new fields now available

* Testing travis

* another travis test

* Still working on travis builds

* Fix missing amazon packages

* Merge in Travis config feature branch (#26)

* Adding version string

* Fixing and cleaning up docker build

* Adding image hashing and verification to 'make all'

* Testing build

* Running gofmt to fix build

* Testing switching travis to make

* Testing build again

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

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

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

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

* Remove needless comment from integration test

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

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

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

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

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

* Forgot to remove spare test

* Add Version Info at Buildtime  (#39)

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

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

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

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

Build time: 20180601_041330Z
Commit: 931d666262

```

* Fixing Broken Docker Build (#41)

Forgot to include go-bindata run in Dockerfile.

* Removing leftover TODO from merge

* Testing releases

* Testing releases

* Testing releases

* Adjusting logging

* Add new group tests to integration tests

* add cleanup to test process

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

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

* Syntax fix

* starting framework for unit tests

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

Fixes a few minor issues:

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

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

* Forgot to run 'go fmt'

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

* Testing not quite ready yet, removing from build

* Add Support For 2 Factor Authentication

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

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

* Expose LDAPS ports in Docker container (#49)

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

* Adding better logging to docker start script

* Add more logging info to docker startup script

* Fix Arm32 Build (#52)

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

fixes #52

* Update docker hub image badge to a working one

previous one simply returned 0.

* Removing 3893 - fixes #50

* Fixing readme MD formatting

* Adding travis_retry to fix against intermittent network outages

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

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

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

* Adding PR template and improving integration test tooling

* Updating formatting

* Add get dependencies step to makefile setup

* Add go 1.11

* Add App Password Support (#60)

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

Fixes #54

* Adding NCoC as official project code of conduct

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

* Properly handling paramaters in logs - fixes #64

* Adding ldapsearch for healthchecks

* fixed quoting in docker startup script (#77)

Thanks @cxcv cxcv

* Minor tweak of help text

* More durable Dockerfile (#92)

Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: Andrea Cervesato <andrea.cervesato@gmail.com>
2020-02-21 09:02:08 +01:00
Icarus Sparry b93d612e36 Fix mutex (#88)
* goimports fixups

Adjust imports lists to match that produced by goimports.

Signed-off-by: Icarus Sparry <icarus.sparry@amd.com>

* Fix mutex handling

Mutexes must not be copied after their first use.

Create a global mutex for ldap and pass a pointer to this around.
Fixes #43

Signed-off-by: Icarus Sparry <icarus.sparry@amd.com>
2020-02-21 08:28:35 +01:00
Andrea Cervesato 63879925c5 More durable Dockerfile (#92) 2019-08-29 21:48:49 -05:00
Travis Groth 0ff1daac96 Support shadowAccount ObjectClass (#84) 2019-07-01 14:46:08 -04:00
Andy Lo-A-Foe bc4ab91888 Fix unkeyed fields. Refs #43 (#80) 2019-07-01 14:24:23 -04:00
Andy Lo-A-Foe b8fd08b92f Remove duplicate. Fixes #71 (#79) 2019-05-20 15:46:31 +02:00
Ben Yanke c9b40b87d4 Minor tweak of help text 2019-04-24 21:13:07 -05:00
Jeppe Ryskov Larsen c69168c3d1 Allow for configuring DN format (#57)
* Add config options for customizing group and nate attribute prefixes and name of ssh-key output

* Rename nameattr and groupattr to respectively nameformat and groupformat

* Messed up setting up default values on merge for ldap and ldaps section

* Fix good-results after 'via LDAP' suffix was removed from gecos and description

* Added uid for good-results aswell

* Revert "Added uid for good-results aswell"

This reverts commit 21b86d2570.

* Adjust tests

* order of cn and uid should be same for both posixaccount and posixgroup to pass tests
2019-03-22 10:45:02 +01:00
Benjamin Schweizer 98ea83c9e0 fixed quoting in docker startup script (#77)
Thanks @cxcv cxcv
2019-03-12 12:25:53 -05:00
Jeppe Ryskov Larsen 2243d3d049 Add config option 'watchconfig' to enable hot-reloading of config on change (#58) 2019-02-15 11:34:50 +01:00
Ben Yanke f793f3a08c Shell script typo fix - thanks @sleleko 2018-12-28 08:16:26 -06:00
Sergey Leleko ba364f12c9 Update host (#70)
For correct work port forwarding in docker
2018-12-28 08:14:44 -06:00
Ben Yanke 28802ff87d Pulling in latest changes and fixes for 1.1.1 (#56)
* Adding a few test dockerfiles

* Continued work on docker builds

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

* Continuing to work on docker build

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

* Implementing dumb init

* Docker build fully switched to alpine

* Setting up alpine build

* Cleaning up dockerfile

* Cleaning up repo in prep for merge

* Updating permissions for clean merge

* Removing old ansible cruft, as per #12

* Removing binaries (since we now build)

* Updating sample config to use new fields now available

* Testing travis

* another travis test

* Still working on travis builds

* Fix missing amazon packages

* Merge in Travis config feature branch (#26)

* Adding version string

* Fixing and cleaning up docker build

* Adding image hashing and verification to 'make all'

* Testing build

* Running gofmt to fix build

* Testing switching travis to make

* Testing build again

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

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

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

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

* Remove needless comment from integration test

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

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

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

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

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

* Forgot to remove spare test

* Add Version Info at Buildtime  (#39)

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

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

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

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

Build time: 20180601_041330Z
Commit: 931d666262

```

* Fixing Broken Docker Build (#41)

Forgot to include go-bindata run in Dockerfile.

* Removing leftover TODO from merge

* Testing releases

* Testing releases

* Testing releases

* Adjusting logging

* Add new group tests to integration tests

* add cleanup to test process

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

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

* Syntax fix

* starting framework for unit tests

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

Fixes a few minor issues:

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

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

* Forgot to run 'go fmt'

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

* Testing not quite ready yet, removing from build

* Add Support For 2 Factor Authentication

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

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

* Expose LDAPS ports in Docker container (#49)

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

* Adding better logging to docker start script

* Add more logging info to docker startup script

* Fix Arm32 Build (#52)

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

fixes #52

* Update docker hub image badge to a working one

previous one simply returned 0.

* Removing 3893 - fixes #50

* Fixing readme MD formatting

* Adding travis_retry to fix against intermittent network outages

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

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

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

* Adding PR template and improving integration test tooling

* Updating formatting

* Add get dependencies step to makefile setup

* Add go 1.11

* Add App Password Support (#60)

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

Fixes #54

* Adding NCoC as official project code of conduct

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

* Properly handling paramaters in logs - fixes #64

* Adding ldapsearch for healthchecks
2018-12-25 11:25:42 -06:00
Ben Yanke 75fa168e9a Merge branch 'master' into dev 2018-12-25 11:24:55 -06:00
Ben Yanke 7a573aad93 Adding ldapsearch for healthchecks 2018-12-25 11:18:53 -06:00
Ben Yanke 9f349d10c8 Properly handling paramaters in logs - fixes #64 2018-11-15 06:56:00 -06:00
kost a02544ac8b Fix Legacy URL (#63) 2018-10-22 11:51:41 -05:00
kost 3acd492e61 uidnumber typo fix (#62) 2018-10-19 22:03:46 -05:00
Ben Yanke bd9a5e47ee Adding NCoC as official project code of conduct
We happily accept contributions based on the merit of the contributions.
2018-09-20 12:38:20 -05:00
Ben Yanke 2e70a47dd6 Adding NCoC as official project code of conduct
We happily accept contributions based on the merit of the contributions.
2018-09-20 12:38:03 -05:00
Ben Yanke e546a1a289 Add App Password Support (#60)
App passwords can now be used to allow easier OTP use alongside applications which need to bind with a static password. Use the key `passappsha256` and specify an array of password hashes. See the readme and sample configuration file for more information.

Fixes #54
2018-09-09 22:41:29 -05:00
Ben Yanke c550eb60e9 Add go 1.11 2018-09-09 20:18:32 -05:00
Ben Yanke 2b4aa3b78d Update README.md 2018-08-30 11:51:52 -05:00
Ben Yanke 6fbd8105cd Add get dependencies step to makefile setup 2018-08-23 00:24:10 -05:00
Ben Yanke a03a26960d Updating formatting 2018-08-22 09:09:53 -05:00
Ben Yanke 45a2671f46 Changing formatting 2018-08-22 09:09:17 -05:00
Ben Yanke 5e1ed70865 Add github PR template 2018-08-22 09:07:47 -05:00
Ben Yanke 3a13387be2 Adding PR template and improving integration test tooling 2018-08-22 08:55:54 -05:00
Ben Yanke e606ecbb56 Merge branch 'master' into dev 2018-08-21 22:58:21 -05:00
Jeppe Ryskov Larsen d9183b8f74 Add TLS options for running both with TLS and without on the same time (#27)
* Add TLS options for running both with TLS and without on the same time.

This commit expands on the settings available for using TLS. It puts TLS settings under the [frontend.tls] section and adds a new setting to [frontend] called TLSExclusive (bool).
TLSExclusive specifies whether or not to only run TLS when it is enabled, and is 'true' by default. Setting it to 'false' and having TLS enabled, causes the server to start both a LDAP and LDAPS server,
and therefore requires to seperate 'listen' options (to run on different ports) - the Frontend.Listen and the Frontend.TLS.Listen. If TLSExclusive is set to 'true' and no Frontend.TLS.Listen is specified, it will use the Frontend.Listen.
2018-08-21 22:18:42 -05:00
Ben Yanke 72c8e84a0d Adding travis_retry to fix against intermittent network outages 2018-08-09 23:18:41 -05:00
Ben Yanke c162ed8366 Fixing readme MD formatting 2018-08-04 22:56:30 -05:00
Ben Yanke 986a9187ee Removing 3893 - fixes #50 2018-08-04 22:14:05 -05:00
Ben Yanke a835e569cd Update docker hub image badge to a working one
previous one simply returned 0.
2018-07-31 16:03:21 -05:00
Ben Yanke d410b01955 Switching docker hub size badge to a working one 2018-07-31 16:02:52 -05:00
Felix Bartels ff6cb9a9d5 Fix Arm32 Build (#52)
As discussed in issue #51, Arm32 builds were using 368 (intel/amd) arch accidentally, generating linux 32 bit binaries instead of arm 32 bit binaries. This commit fixes this in the Makefile, which will fix both the local builds, as well as the travis CI and release builds.

fixes #52
2018-07-22 12:16:20 -05:00
Ben Yanke 6516741b3c Add more logging info to docker startup script 2018-07-21 20:17:40 -05:00
Ben Yanke 5c1a39bd47 Adding better logging to docker start script 2018-07-21 16:18:13 -05:00
Andreas Krüger a21d93bbd7 Expose LDAPS ports in Docker container (#49)
Currently, the LDAPS ports are not exposed in the docker container.
2018-07-21 07:31:07 -05:00
Ben Yanke 2020579614 Merging branches 2018-07-20 23:24:59 -05:00
Jeppe Ryskov Larsen 47401aac5f Add Support For 2 Factor Authentication
Merging in feature from @ryskov (PR #24) adding 2FA support during LDAP binds. This is accomplished by concatenating the code to the end of the password. 

Also added integration tests for the TOTP method to run in CI. Could not, however, add automated tests for the yubikey, due the physical nature.
2018-07-20 23:08:12 -05:00
Ben Yanke 6cd27aa790 Testing not quite ready yet, removing from build 2018-06-15 23:43:40 -05:00
Ben Yanke 9495943701 Adding codecov for go now that a single test is written 2018-06-15 23:38:42 -05:00
Ben Yanke 8de56c87a9 Forgot to run 'go fmt' 2018-06-15 23:35:18 -05:00
Ben Yanke 208fb1e33d Merge branch 'dev' of github.com:glauth/glauth into dev 2018-06-15 23:29:49 -05:00
Ben Yanke 32a13ec85a Merging in ongoing progress from Feature/travis build (#44)
Fixes a few minor issues:

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

Fixing a simple issue found by go vet. Need to do more work on fixing issues found in go vet.
2018-06-15 23:29:45 -05:00
Ben Yanke a75743ef7d starting framework for unit tests 2018-06-15 23:29:15 -05:00
Ben Yanke 93a6a5542f Syntax fix 2018-06-07 11:18:30 -05:00
Ben Yanke c431a6e426 Auto fetching bindata during build so it's not needed to be done manually 2018-06-07 11:14:27 -05:00
Ben Yanke a9837f3112 Add documentation about otherGroups, which was a previously undocumented field. 2018-06-06 12:49:56 -05:00
Ben Yanke 2d7a2ab8ce add cleanup to test process 2018-06-05 21:10:30 -05:00
Ben Yanke bc94f47c9e Add new group tests to integration tests 2018-06-05 20:21:42 -05:00
Ben Yanke 39b50ec92a Adjusting logging 2018-06-05 12:22:49 -05:00
Ben Yanke 50a78e3c56 Testing releases 2018-06-01 22:02:47 -05:00
Ben Yanke 5e2114e04b Testing releases 2018-06-01 21:55:56 -05:00
Ben Yanke 706c144f82 Testing releases 2018-06-01 21:47:01 -05:00
Ben Yanke 5519634a3e Removing leftover TODO from merge 2018-06-01 12:49:49 -05:00
Ben Yanke b32db95f4f Fixing Broken Docker Build (#41)
Forgot to include go-bindata run in Dockerfile.
2018-05-31 23:32:24 -05:00
Ben Yanke 0b1c979247 Add Version Info at Buildtime (#39)
Adds the build info to the binary at buildtime via buildtime variables. This is shown by `./glauth --version`.

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

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

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

Build time: 20180601_041330Z
Commit: 931d666262

```
2018-05-31 23:18:12 -05:00
Ben Yanke b04ff2dd67 Forgot to remove spare test 2018-05-24 12:52:57 -05:00
Jeppe Ryskov Larsen 94574e50a7 Add support for including groups in groups (#23)
* Add support for including groups in groups
* Pulling in Makefile structure to allow easier testing and builds
2018-05-24 12:50:47 -05:00
Ben Yanke 1155d214ed Adding back config file to fix build. Previous commit intended to see if build failed (and it did) 2018-05-14 12:49:48 -05:00
Ben Yanke 9ae4613a7f Switching from 1 sec to 2 sec timeout for integration test 2018-05-14 12:44:14 -05:00
Ben Yanke 8d808c80a0 Adding 'is-process-running' check before integration tests run, to clearly note if the program crashes on run 2018-05-13 22:05:05 -05:00
Ben Yanke 5728484e61 Remove needless comment from integration test 2018-05-12 21:01:48 -05:00
Ben Yanke 6df835dd68 Merging in Fixes from travis-build feature branch (#33)
This includes an integration test which runs glauth and compares ldapsearch snapshot output stored in the repo compared to the snapshot output of the glauth.

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

While the mac, linux-arm, and windows binaries are not able to be run in travis, they are able to be at least compiled.
2018-05-12 20:55:32 -05:00
Ben Yanke 80883e4a57 Testing build again 2018-05-12 19:37:46 -05:00
Ben Yanke 02970c82ff Testing switching travis to make 2018-05-12 19:32:59 -05:00
Ben Yanke 7124c108a7 Running gofmt to fix build 2018-05-12 19:29:37 -05:00
Ben Yanke 136d7d7833 Testing build 2018-05-12 19:25:54 -05:00
Ben Yanke f0cf6488e4 Adding image hashing and verification to 'make all' 2018-05-11 22:58:18 -05:00
Ben Yanke dc90cd6819 Fixing and cleaning up docker build 2018-05-10 22:28:03 -05:00
Ben Yanke c2aca63246 Adding version string 2018-05-10 22:17:26 -05:00
Ben Yanke 60ac4368c6 Merge in Travis config feature branch (#26) 2018-05-03 23:45:33 -05:00
Ben Yanke 64fbf20dcd Merge pull request #22 from pasientskyhosting/amazon_fix_dev
Fix missing amazon packages
2018-05-03 22:48:48 -05:00
Jeppe Larsen de571e8cb6 Fix missing amazon packages 2018-05-03 16:02:46 +02:00
Ben Yanke c232ee0476 Adding travis builds to dev
Adding travis builds
2018-04-28 23:18:55 -05:00
Ben Yanke 4f82bf2baf Still working on travis builds 2018-04-28 23:12:59 -05:00
Ben Yanke 7608bc0133 another travis test 2018-04-28 16:22:50 -05:00
Ben Yanke 2ae84a9034 Testing travis 2018-04-28 16:05:30 -05:00
Ben Yanke ac85255d86 Updating sample config to use new fields now available 2018-04-25 13:05:27 -05:00
Ben Yanke 2a16b13a01 Removing binaries (since we now build) 2018-04-25 13:05:07 -05:00
Ben Yanke 5b84deb1c1 Removing old ansible cruft, as per #12 2018-04-25 12:30:55 -05:00
Ben Yanke c5cc0f5572 Merge pull request #19 from nmcclain/feature/dockerfile
Merging in Docker Build Tools
2018-04-22 22:59:22 -05:00
Ben Yanke dd71d9ac4e Updating permissions for clean merge 2018-04-22 22:57:39 -05:00
Ben Yanke 5f5c12e78d Cleaning up repo in prep for merge 2018-04-22 22:56:57 -05:00
Ben Yanke d5a1b6f216 Cleaning up dockerfile 2018-04-22 22:51:51 -05:00
Ben Yanke 82bff3fc36 Setting up alpine build 2018-04-22 22:49:14 -05:00
Ben Yanke fd9aaa243d Docker build fully switched to alpine 2018-04-22 22:41:08 -05:00
Ben Yanke e2c9210beb Implementing dumb init 2018-04-22 11:11:24 -05:00
Ben Yanke e03e9216fa Removing testing from build script, since it belongs later in process 2018-04-22 10:34:32 -05:00
Ben Yanke 70ff3600ec Continuing to work on docker build 2018-04-22 10:33:31 -05:00
Ben Yanke 68edcdc228 Basic example of docker build and run works - see build.sh for now 2018-04-21 22:33:36 -05:00
Ben Yanke f076f868dc Continued work on docker builds 2018-04-21 22:33:36 -05:00
Ben Yanke f039bbb964 Adding a few test dockerfiles 2018-04-18 21:41:19 -05:00
78 changed files with 6461 additions and 1037 deletions
+25
View File
@@ -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.
+41
View File
@@ -0,0 +1,41 @@
name: docker
on:
pull_request:
push:
branches:
- master
release:
types:
- created
- edited
jobs:
build-push:
runs-on: ubuntu-latest
strategy:
fail-fast: false
env:
BUILDX_VERSION: v0.4.1
DOCKER_CLI_EXPERIMENTAL: enabled
steps:
- uses: actions/checkout@v2
- name: set up multiarch builder
run: |
set -ex
curl -sSL https://get.docker.com | bash
mkdir -p ~/.docker/cli-plugins
curl -sSLo ~/.docker/cli-plugins/buildx https://github.com/docker/buildx/releases/download/$BUILDX_VERSION/buildx-$BUILDX_VERSION.linux-amd64
chmod +x ~/.docker/cli-plugins/buildx
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker buildx create --name multiarch
docker buildx use multiarch
docker buildx inspect --bootstrap
curl -sSL https://gist.githubusercontent.com/ArturKlauser/0f0293c62f5626df0261ac994d8a46af/raw/33ef25b8a93f15310f0a4013deca3f8d20fc00a7/check-qemu-binfmt.sh | bash
- name: log in github registry
run: docker login --username "${{ github.actor }}" --password "${{ secrets.GITHUB_TOKEN }}" docker.pkg.github.com
- name: build multiarch images
run: docker buildx build -t docker.pkg.github.com/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]'):$(echo ${{ github.ref }} | tr / -) --platform linux/amd64,linux/arm64,linux/arm/v7 .
- name: push multiarch images
if: github.event.release.tag_name
run: docker buildx build --push -t docker.pkg.github.com/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]'):${{ github.event.release.tag_name }} --platform linux/amd64,linux/arm64,linux/arm/v7 .
+6 -1
View File
@@ -4,12 +4,17 @@ glauth.cfg
# Ignore the built binary during testing
glauth
# Ignore generated certs in this folder
certs/
# Built binaries
bin
bindata.go
pkg/assets/bindata.go
.*.swp
# Ignoring travis CLI tool logs
*travis.log
certs/
+18 -15
View File
@@ -1,10 +1,14 @@
os: linux
dist: xenial
language: go
# USE THIS alongside makefile tag detection: $TRAVIS_TAG
# if you change the version here, also update the travis deploy condition
go:
- 1.9.x # last version
- 1.10.x # current version
- 1.13.x # older version
- 1.14.x # last version
- 1.15.x # current version
# Speed up git clones
git:
@@ -15,18 +19,19 @@ git:
# flunk the build and immediately stop. It's sorta like having
# set -e enabled in bash.
before_script:
- sudo apt-get -qq update && sudo apt-get install -y ldap-utils oathtool # need this for integration tests
- sudo apt-get -qq update && sudo apt-get install -y go-bindata # need this for makefile
- 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/
#- 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 # Not sure what this is
#- 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:
@@ -62,17 +67,15 @@ script:
deploy:
provider: releases
api_key:
secure: CsQb802CJJlum5suwo3Yvw1TkmX56O6TPrtJqMI0Qm6A878dhrZIjyVmfG3Iq9rtZvfFhK+Y3SIfPZp8BjV3cicc+X1Yh1VvMG12ukqHSruvGvLG49dtnq40P31u85Y4HdBk+W8YIsCQHEonsID9l+q2LbhuiC358UF+HNgzESEz6ZyTZz0AvAJzm1uyiUiPTHMFwqN7qQsookmLXf490rqp9YmFFqte+o3/JwoYXc3pE/rBMJitPSJNGULzu2QofV5gJ0JxgPWlfF5DsHHEi1jhpBcTfiFzEvkdcl1SVme9MMNPue7j8u9+PLJ6uIdlnTeBcMPKgtzOg/kBqY4+3QzY4aPXjKm3+MF3Cau66+g3QC7dKenvYefapQYN7t1SSpiR9X3TLWZrBbw2ovMe3em+uMnIM08bemzj8DapRgKXmYKWnwTlA9KLf0Vl22255OzMGarkhx94cDvUHmEkNo7qBXqmUxPo0Fc5XJyD7JmUNgYoWj0/NjyN09VG22FL2FW44HR3eitq2wSFSMO/jsC2rLuCnyLHViP+O2YJu0T4Q3gdQUcdQmjT7y8ogH5Shn9t2wY4NRxI3MyXO7+fGEOj1DaD6CFQwcSwbJVpIzS6J8z7+rRrzE9QkXeCmwNNt5UkpR9lYmOEzpc00rTRimwjfmJn/nTXsIDiQg9iZJM=
token:
secure: lPI5FftcQB1eHihN9daeFOT97vZcfq2w0hRb6amC6U4P+K7Ih23R9NmWhDuvGM9CPLaAqgOKiHWIUnP4q9EeBeAUMgg6FI2syr/0nDYz5cHqZMMPWFgeDvn+HqfMdFIVeiKT80+uGY/JNsMI8lsJ0eILEp0GpyIZkoAewCEyxJ/fcLM5htGnQIQ8mQ+2ZXflvPofv6/RPHgu3ryC0icJFVMREIivp2QIZBpFnawnI+OE8ZzxD5R05gfg3IJtYtc/4diX4RPyqVTCqljU/x7/g9KgNsnaLGEz37pBUS0IQcq4ITX/cm4y5LLWPV6JugxjyTq1SXKJnHEgfEFk5/hPM7Q1DMZJ5tGpb2B4aSMwB6YHwvVW83ih96Vw8ZgIrxWT9LyF4n9AHakLhDIiJieDXa3dvG7U5oTxlSP/RJkXlMZEJ3lS/1RZ9vvQ0EOEMAO6ePQzSZ4EM/UShFalBI+fqDtzqxFGeeCWw6lwiKLF2GF7enT8+wLsWOMI43JM5uPAq2g0KFMtJEEcK08IFraObvK5bcFH7snCDtsAaLyjdkVIIBteNtfDsXQ8PMi4bQpLr5kpY68dkyuxDr4IpL1GKqlpe2LbjkzsGrq5wifcHMFmUkEfY/6/ELFc9fL+1ZnMAe7YiW41AvX+hrgVmc3M8zp1kVvy2oERV4LfV1vfaEU=
file_glob: true
file: bin/*
draft: true
name: "DRAFT RELEASE: $TRAVIS_TAG"
body: "Draft release, committed with message: $TRAVIS_COMMIT_MESSAGE"
body: "# Commit Details<br />$TRAVIS_COMMIT_MESSAGE<br /><br /># Note About Non-Linux 64bit Files<br /> While I expect the code is system-independent, there is not a good (and free) CI system which can be easily used to continuously test releases on ARM, BSD, Linux-32bit, and Windows.<br /><br />As such, the primary release platform is Linux 64 bit. The extent of testing on these packages consists solely of cross-compiling for these architectures from a linux 64 bit system.<br /><br />We will accept PRs which fix bugs on these platforms, but be aware these binaries will not be tested regularly, and instead are provided for the convenience of those who feel comfortable using them with this knowledge."
skip_cleanup: true
cleanup: true
on:
branch: master
tags: true
condition: $TRAVIS_GO_VERSION = "1.10.1"
condition: $TRAVIS_GO_VERSION =~ ^1\.15
repo: glauth/glauth
+5
View File
@@ -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.
+20 -9
View File
@@ -15,24 +15,32 @@ WORKDIR /app
ENV GOPATH=/tmp/gocode
ENV GOOS=linux
ENV GOARCH=amd64
ENV CGO_ENABLED=0
# Only needed for alpine builds
RUN apk add --no-cache git bzr make
RUN apk add --no-cache git make go-bindata
# Run go-bindata to embed data for API
RUN go-bindata -pkg=assets -o=pkg/assets/bindata.go assets
RUN gofmt -w pkg/assets/bindata.go
# Install deps
RUN go get -d -v ./...
# Run go-bindata to embed data for API
RUN go get -u github.com/jteeuwen/go-bindata/... && $GOPATH/bin/go-bindata -pkg=main assets && gofmt -w bindata.go
# Build and copy final result
RUN make linux64 && cp ./bin/glauth64 /app/glauth
RUN uname -a
RUN if [ $(uname -m) == x86_64 ]; then make linux64 && cp ./bin/glauth64 /app/glauth; fi
RUN if [ $(uname -m) == aarch64 ]; then make linuxarm64 && cp ./bin/glauth-arm64 /app/glauth; fi
RUN if [ $(uname -m) == armv7l ]; then make linuxarm32 && cp ./bin/glauth-arm32 /app/glauth; fi
# Check glauth works
RUN /app/glauth --version
#################
# 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
@@ -45,11 +53,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"]
+27 -16
View File
@@ -1,8 +1,8 @@
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)
BUILD_TIME=$(shell date -u +%Y%m%d_%H%M%SZ)
GIT_CLEAN=$(shell git status | grep -E "working (tree|directory) clean" | wc -l | sed 's/^[ ]*//')
# Last git tag
LAST_GIT_TAG=$(shell git describe --abbrev=0 --tags 2> /dev/null)
@@ -14,8 +14,12 @@ GIT_IS_TAG_COMMIT=$(shell git describe --abbrev=0 --tags > /dev/null 2> /dev/nul
GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
# Build variables
BUILD_VARS=-X main.GitCommit=${GIT_COMMIT} -X main.GitBranch=${GIT_BRANCH} -X main.BuildTime=${BUILD_TIME} -X main.GitClean=${GIT_CLEAN} -X main.LastGitTag=${LAST_GIT_TAG} -X main.GitTagIsCommit=${GIT_IS_TAG_COMMIT}
BUILD_FILES=glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
BUILD_VARS=-s -w -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}"
# Plugins
include pkg/plugins/Makefile
#####################
# High level commands
@@ -37,7 +41,7 @@ fast: setup linux64 verify cleanup
binaries: linux32 linux64 linuxarm32 linuxarm64 darwin64 win32 win64
# Setup commands to always run
setup: bindata format
setup: bindata getdeps format
#####################
# Subcommands
@@ -47,42 +51,49 @@ setup: bindata format
runtest:
./scripts/travis/integration-test.sh cleanup
# Get all dependencies
getdeps:
go get -d ./...
updatetest:
./scripts/travis/integration-test.sh
bindata:
go get -u github.com/jteeuwen/go-bindata/... && ${GOPATH}/bin/go-bindata -pkg=main assets && gofmt -w bindata.go
go get -u github.com/jteeuwen/go-bindata/... && ${GOPATH}/bin/go-bindata -pkg=assets -o=pkg/assets/bindata.go assets && gofmt -w pkg/assets/bindata.go
cleanup:
rm bindata.go
rm pkg/assets/bindata.go
format:
go fmt
devrun:
go run ${BUILD_FILES} -c glauth.cfg
go run ${BUILD_FILES} -c sample-simple.cfg
linux32:
GOOS=linux GOARCH=386 go build -ldflags "${BUILD_VARS}" -o bin/glauth32 ${BUILD_FILES} && cd bin && sha256sum glauth32 > glauth32.sha256
GOOS=linux GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth32 ${BUILD_FILES} && cd bin && sha256sum glauth32 > glauth32.sha256
linux64:
GOOS=linux GOARCH=amd64 go build -ldflags "${BUILD_VARS}" -o bin/glauth64 ${BUILD_FILES} && cd bin && sha256sum glauth64 > glauth64.sha256
GOOS=linux GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth64 ${BUILD_FILES} && cd bin && sha256sum glauth64 > glauth64.sha256
linuxarm32:
GOOS=linux GOARCH=386 go build -ldflags "${BUILD_VARS}" -o bin/glauth-arm32 ${BUILD_FILES} && cd bin && sha256sum glauth-arm32 > glauth-arm32.sha256
GOOS=linux GOARCH=arm go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-arm32 ${BUILD_FILES} && cd bin && sha256sum glauth-arm32 > glauth-arm32.sha256
linuxarm64:
GOOS=linux GOARCH=arm64 go build -ldflags "${BUILD_VARS}" -o bin/glauth-arm64 ${BUILD_FILES} && cd bin && sha256sum glauth-arm64 > glauth-arm64.sha256
GOOS=linux GOARCH=arm64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-arm64 ${BUILD_FILES} && cd bin && sha256sum glauth-arm64 > glauth-arm64.sha256
darwin64:
GOOS=darwin GOARCH=amd64 go build -ldflags "${BUILD_VARS}" -o bin/glauthOSX ${BUILD_FILES} && cd bin && sha256sum glauthOSX > glauthOSX.sha256
GOOS=darwin GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauthOSX ${BUILD_FILES} && cd bin && sha256sum glauthOSX > glauthOSX.sha256
darwinarm64:
GOOS=darwin GOARCH=arm64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauthOSX-arm64 ${BUILD_FILES} && cd bin && sha256sum glauthOSX-arm64 > glauthOSX-arm64.sha256
win32:
GOOS=windows GOARCH=386 go build -ldflags "${BUILD_VARS}" -o bin/glauth-win32 ${BUILD_FILES} && cd bin && sha256sum glauth-win32 > glauth-win32.sha256
GOOS=windows GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win32 ${BUILD_FILES} && cd bin && sha256sum glauth-win32 > glauth-win32.sha256
win64:
GOOS=windows GOARCH=amd64 go build -ldflags "${BUILD_VARS}" -o bin/glauth-win64 ${BUILD_FILES} && cd bin && sha256sum glauth-win64 > glauth-win64.sha256
GOOS=windows GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win64 ${BUILD_FILES} && cd bin && sha256sum glauth-win64 > glauth-win64.sha256
verify:
+133 -28
View File
@@ -1,27 +1,30 @@
# GLAuth: LDAP authentication server for developers
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
[![Travis Build - Master](https://img.shields.io/travis/glauth/glauth.svg)](https://travis-ci.org/glauth/glauth)
[![Last Commit](https://img.shields.io/github/last-commit/glauth/glauth.svg)](https://github.com/glauth/glauth/graphs/commit-activity)
[![Gitter](https://badges.gitter.im/glauth/community.svg)](https://gitter.im/glauth/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Matrix](https://img.shields.io/badge/chat-%2fjoin%20%23glauth_community:gitter.im-green)](hey)
[![](https://img.shields.io/docker/build/glauth/glauth.svg)](https://hub.docker.com/r/glauth/glauth/)
[![DockerHub Image Size](https://img.shields.io/imagelayers/image-size/glauth/glauth/latest.svg)](https://hub.docker.com/r/glauth/glauth/)
![GitHub all releases](https://img.shields.io/github/downloads/glauth/glauth/total)
![Docker pulls](https://badgen.net/docker/pulls/glauth/glauth)
[![Maintainability](https://img.shields.io/codeclimate/maintainability/glauth/glauth.svg)](https://codeclimate.com/github/glauth/glauth/maintainability)
[![Test Coverage](https://img.shields.io/codeclimate/coverage/glauth/glauth.svg)](https://codeclimate.com/github/glauth/glauth/test_coverage)
![Travis (.com) branch](https://img.shields.io/travis/com/glauth/glauth/dev)
![Docker Automated build](https://img.shields.io/docker/automated/glauth/glauth)
[![Donate via Paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/benyanke)
![GitHub last commit (branch)](https://img.shields.io/github/last-commit/glauth/glauth/dev)
![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability-percentage/glauth/glauth)
* 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.
* Store your user directory in a local file, S3 or proxy to existing LDAP servers.
* Store your user directory in a file, local or in S3; SQL database; or proxy to existing LDAP servers.
* Two Factor Authentication (transparent to applications)
* Multiple backends can be chained to inject features
Use it to centralize account management across your Linux servers, your OSX machines, and your support applications (Jenkins, Apache/Nginx, Graylog2, and many more!).
### Contributing
Please base all PRs on [dev](https://github.com/nmcclain/glauth/tree/dev), not master.
- Please base all Pull Requests on [dev](https://github.com/glauth/glauth/tree/dev), not master.
- Format your code autonmatically using `gofmt -d ./` before committing
### Quickstart
This quickstart is a great way to try out GLAuth in a non-production environment. *Be warned that you should take the extra steps to setup SSL (TLS) for production use!*
@@ -29,19 +32,22 @@ This quickstart is a great way to try out GLAuth in a non-production environment
1. Download a precompiled binary from the [releases](https://github.com/glauth/glauth/releases) page.
2. Download the [example config file](https://github.com/glauth/glauth/blob/master/sample-simple.cfg).
3. Start the GLAuth server, referencing the path to the desired config file with `-c`.
- `sudo ./glauth64 -c sample-simple.cfg`
- `./glauth64 -c sample-simple.cfg`
4. Test with traditional LDAP tools
- 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`
- For example: `ldapsearch -LLL -H ldap://localhost:3893 -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 plugins* - build additional (SQL) plugin backends
*make test* - run the integration test on linux64 binary
### Usage:
@@ -58,6 +64,10 @@ Options:
-K <aws_key_id> AWS Key ID.
-S <aws_secret_key> AWS Secret Key.
-r <aws_region> AWS Region [default: us-east-1].
--ldap <address> Listen address for the LDAP server.
--ldaps <address> Listen address for the LDAPS server.
--ldaps-cert <cert-file> Path to cert file for the LDAPS server.
--ldaps-key <key-file> Path to key file for the LDAPS server.
-h, --help Show this screen.
--version Show version.
```
@@ -74,15 +84,21 @@ Here's a sample config wth hardcoded users and groups:
baseDN = "dc=glauth,dc=com"
[[users]]
name = "hackers"
unixid = 5001
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
sshkeys = [ "ssh-dss AAAAB3..." ]
[[users]]
name = "uberhackers"
uidnumber = 5006
primarygroup = 5501
passbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
[[groups]]
name = "superheros"
unixid = 5501
gidnumber = 5501
```
To create the password SHA hash, use this command: `echo -n "mysecret" | openssl dgst -sha256`
Instead of a local configuration file, GLAuth can fetch its configuration from S3. This is an easy way to ensure redundant GLAuth servers are always in-sync.
```unix
glauth -c s3://bucketname/glauth.cfg
@@ -92,14 +108,34 @@ 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
### Chaining backends
This can be used, for instance, to inject support for Two Factor Authentication for backends that do not support the feature natively:
```
[[backends]]
datastore = "ldap"
servers = ["ldap:s//localhost:390"]
[[backends]]
datastore = "config"
...
[[users]]
name = "hackers"
otpsecret = "................"
```
### 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
@@ -137,28 +173,42 @@ More configuration options are documented here: https://github.com/nmcclain/glau
* Specify OTP secret used to validate OTP passcode
* Example: 3hnvnk4ycv44glzigd6s25j4dougs3rk
* default = blank
* passappbcrypt
* 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
* 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
### Strong Passwords
If you are currently using sha256 passwords (`passsha256` or `passappsha256`) moving to strong, salted paswords is recommended. Simply switch to `passbcrypt` and/or `passappbcrypt` password types. Currently (2021) 2<sup>12</sup> is a reasonably good value, depending our your server's CPU.
### Two Factor Authentication
GLAuth can be configured to accept OTP tokens as appended to a users password. Support is added for both **TOTP tokens** (often known by it's most prominent implementation, "Google Authenticator") and **Yubikey OTP tokens**.
When using 2FA, append the 2FA code to the end of the password when authenticating. For example, if your password is "monkey" and your otp is "123456", enter "monkey123456" as your password.
When using 2FA, append the 2FA code to the end of the password when authenticating. For example, if your password is "monkey" and your otp is "123456", enter "monkey123456" as your password.
#### TOTP Configuration
To enable TOTP authentication on a user, you can use a tool [like this](https://freeotp.github.io/qrcode.html) to generate a QR code (pick 'Timeout' and optionally let it generate a random secret for you), which can be scanned and used with the [Google Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en) app. To enable TOTP authentication, configure the `otpsecret` for the user with the TOTP secret.
#### 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.
Now configure 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.
@@ -183,7 +233,7 @@ Any of the architectures above will work for production. Just remember:
A small note about other architectures: while I expect the code is, for the most part, system-independent, there is not a good (and free) CI system which can be easily used to continuously test releases on ARM, BSD, Linux-32bit, and Windows. As such, all of the non-linux-64bit packages are provided as is. The extent of testing on these packages consists solely of cross-compiling for these architectures from a linux 64 bit system.
We will accept PRs which fix bugs on these platforms, but be aware these binaries will not be tested regularly, and instead are provided for the convenience of those who feel comfortable with this.
### Building:
You'll need go-bindata to build GLAuth. Then use the Makefile.
```unix
@@ -191,15 +241,70 @@ go get github.com/jteeuwen/go-bindata/...
make all
```
### Support
# Logging
- using logr with increasing verbosity
- 0 you always want to see this
- 1 common logging that you might *possibly* want to turn off (error)
- 2 warn
- 3 notice
- 4 info
- 6 debug
- 8 trace
- 10 I would like to performance test your log collection stack
- errors really are errors that cannot be handled or returned
- returning a proper LDAP error code is handling an error
Support the ongoing development of GLAuth!
# Testing
**Paypal**
Of course, a core set of tests is being run by Travis CI. However, when developing new features/refactoring, a more comprehensive regression testing suite is needed.
[![Donate via Paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/benyanke)
You can run `go test` to execute the tests found in `glauth_test.go` -- better, if it is installed, you can run [goconvey](https://github.com/smartystreets/goconvey)
Since some tests cover TOTP, you will first need to install `oathtool` in your environment.
**Bitcoin Address**
In order to test GLAuth against an LDAP backend, you will need docker. Run this command:
```
docker run \
--rm \
-d \
-p 389:389 \
--name openldap-service \
--hostname ldap-service \
--env LDAP_ORGANISATION="GLauth" \
--env LDAP_DOMAIN="glauth.com" \
--env LDAP_ADMIN_PASSWORD="password" \
--env LDAP_CONFIG_PASSWORD="password" \
--env LDAP_BASE_DN="dc=glauth,dc=com" \
-v $PWD/misc/openldap/config:/etc/ldap/slapd.d \
-v $PWD/misc/openldap/db:/var/lib/ldap \
osixia/openldap:latest
```
Refer to [this page](https://github.com/glauth/glauth/wiki/Quick-openldap-setup-to-test-LDAP-backend) for a somewhat more in-depth overview of testing with OpenLDAP.
# Compatibility
While our stated goal for GLAuth is to provide the simplest possible authentication server, we keep finding an increasing number of client appliances that are asking fairly "existential" questions of the server. We have been working on providing answers these clients will find satisfactory.
### Root DSE
RFC 4512: "An LDAP server SHALL provide information about itself and other information that is specific to each server. This is represented as a group of attributes located in the root DSE"
Test: `ldapsearch -LLL -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -s base "(objectclass=*)"`
### Subschema Discovery
RFC 4512: "To read schema attributes from the subschema (sub)entry, clients MUST issue a Search operation [RFC4511] where baseObject is the DN of the subschema (sub)entry..."
Test: `ldapsearch -LLL -o ldif-wrap=no -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bcn=schema -s base`
By default, this query will return a very minimal schema (~5 objects) -- you can ask GLAuth to return more comprehensive schemas by unpacking, in the `schema/` directory, the OpenLDAP or FreeIPA schema archives found in the `assets/` directory.
### LDAP Backend: "1.1" attribute
RFC 4511: "A list containing only the OID "1.1" indicates that no attributes are to be returned."
## Stargazers over time
[![Stargazers over time](https://starchart.cc/glauth/glauth.svg)](https://starchart.cc/glauth/glauth)
39z2Zkoc24LsuiqCQNFe7QrX4da3mzbGjK
Binary file not shown.
Binary file not shown.
+283
View File
@@ -0,0 +1,283 @@
// Code generated by go-bindata.
// sources:
// assets/glauth.css
// assets/glauth.js
// assets/index.html
// DO NOT EDIT!
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(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)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _assetsGlauthCss = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x8f\x41\x6e\xc3\x20\x10\x45\xf7\x9c\x62\x24\xaf\x63\xd9\x0e\x4e\xc1\x39\x0d\x03\x63\x9b\xd6\x01\x04\xa4\xad\x5a\xf5\xee\x95\x1d\x39\x2a\xc9\xa6\xeb\xff\xfe\x7f\x33\xac\x7e\xbd\x5e\xd0\xe7\xe8\x1d\x7c\x33\x00\xed\x17\x1f\x07\xa8\x74\x73\x94\x1d\x9e\x19\x00\x2a\xfd\x36\x45\x7f\x75\xe6\xb0\x87\xa4\xc7\x66\x6c\xcf\xec\x87\xb1\x2a\x65\x95\x53\x50\x8e\x96\xa2\x7f\xe4\x5c\xf6\xf4\x84\xd4\x17\xca\xd1\xea\x07\x54\x0a\x83\x7f\xd0\x22\x95\xd8\x4b\x3c\xad\x87\x04\x65\x8c\x75\xd3\x00\x22\x7c\xae\x74\x95\x28\xbe\x53\xfc\x2f\xce\xe6\x6e\x25\x77\xb0\x93\xa2\x41\x79\xb3\x66\x65\x17\xc8\x73\x31\xf4\x32\x0a\x2d\xcc\xe6\xb9\xc5\x66\x8b\xef\xab\x7c\x5d\x7d\xfe\x17\x60\xf4\x2e\x1f\x92\xfd\xa2\x01\xda\x5d\xbd\x4f\xd4\x3e\x14\x12\x41\x9c\x2b\x73\x6f\x7d\x90\x9d\xe6\x3c\x00\xfa\xa5\x30\xd7\x3e\xd4\x68\x9d\x29\xba\xed\x49\x35\xa2\x7f\xc4\x12\xa9\xa8\xcb\x4f\x5a\x85\x5a\xea\xed\x8e\xdf\x00\x00\x00\xff\xff\x91\x33\xcc\x6e\xee\x01\x00\x00")
func assetsGlauthCssBytes() ([]byte, error) {
return bindataRead(
_assetsGlauthCss,
"assets/glauth.css",
)
}
func assetsGlauthCss() (*asset, error) {
bytes, err := assetsGlauthCssBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "assets/glauth.css", size: 494, mode: os.FileMode(436), modTime: time.Unix(1524616076, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _assetsGlauthJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x54\xd1\x8a\xdb\x30\x10\x7c\xf7\x57\x6c\xdd\xc0\xc9\x34\x95\x43\x5f\x43\xfa\x76\x70\x29\xf4\x0a\x6d\x7e\x40\xb1\xd6\x89\xa8\x2c\x99\xf5\xc6\x69\x39\xfc\xef\x45\x96\x9d\xf8\x72\x77\x70\xc5\x2f\x49\xd8\x9d\x99\x8c\x35\x63\xb5\x8a\xc0\x38\x46\x6a\x95\x85\x0d\x7c\x59\xad\x56\xeb\x24\x59\x88\xf2\xe4\x0a\x36\xde\x89\x0c\x9e\x12\x80\x53\xad\x15\xe3\x77\x64\x32\x45\x23\xb2\x75\x02\x90\xe7\x71\xb8\x53\xc6\x86\x49\x97\xad\x93\x24\xc8\xb1\x32\x76\xab\x61\x03\x41\x69\xd4\xb9\x55\xe8\x55\x03\xba\x8a\x13\xd8\xc0\x42\x1e\x90\xbf\xfd\xfa\xf1\x28\x20\xcd\x35\xee\x4f\x87\xbc\x55\xd4\xa4\x4b\xb8\x98\xd1\x91\x17\x99\x8c\x55\x6d\x15\x23\x6c\xe0\x41\x39\x6d\x71\xaf\xa8\x91\x85\xaf\x6a\x63\x51\xc0\x42\xa4\x1f\x1b\x56\xdc\xd4\xca\xa1\xfd\x3c\xa2\xd3\x4c\x1e\xb9\xb2\x22\x83\xfe\x29\xa2\x54\x98\x84\xdf\x9b\x8b\xa8\xd0\xc3\xfa\xb9\xcc\xc8\x0e\x1f\x13\xfe\x7b\xad\xcc\x77\x31\xc3\x00\x52\x8b\x34\xd3\x42\xd4\x78\x61\xa2\xcb\x12\x00\x59\x86\x26\xdc\x14\xe7\xad\x03\x4c\xef\x89\x3c\x7d\x48\x27\x74\x65\xcf\xea\x6f\xf3\x52\xa0\x41\xde\x99\x0a\xfd\x89\xc5\xb3\x12\x2d\xc7\xde\x0e\x1a\xeb\xa4\xbb\xad\x5b\xec\xe6\xa5\x6b\xa1\x99\xb7\x45\x0b\xb3\x3c\x85\x4f\x63\x6d\x5f\x6b\x9b\x96\xa5\xa7\x7b\x55\x1c\xaf\xe6\x70\xdc\x01\x98\x12\x04\xca\xad\x86\xaf\x83\xc6\x75\x05\xd7\x97\x21\x20\xd6\xc3\xb8\xeb\xbf\xbb\xff\xcb\x2f\x28\xcd\x08\xaf\xcf\x4a\x1e\x8d\x46\x91\xc9\x9a\xb0\x46\xa7\x77\x7e\xd0\x4d\x33\x59\x2a\x8d\x5b\x27\xc2\x05\xf0\xae\x48\x07\xda\xec\x30\x43\x44\xaf\x25\x99\x4c\xce\x81\xf0\x60\x1a\x46\x7a\x40\x5b\x23\x89\xbb\xc0\xfb\x89\x56\xb1\x69\xf1\x6e\x12\x58\xe1\x1d\xe3\x1f\x5e\xc2\xde\xfa\xe2\x77\xfc\xcb\x90\xce\xd9\x38\xed\xcf\xb2\xf2\x15\x3a\x1e\x9d\xe4\x39\x21\x9f\xc8\x41\x1c\x8f\xe4\x4c\x96\xe4\xab\x47\x7f\x16\xc3\xd1\xbd\x85\xf2\x54\x29\x8e\xa0\x0e\x6d\x83\x4f\x53\xf4\x00\xeb\x97\xc3\xd5\xf8\x2f\x00\x00\xff\xff\x1d\x12\x43\xad\x6a\x05\x00\x00")
func assetsGlauthJsBytes() ([]byte, error) {
return bindataRead(
_assetsGlauthJs,
"assets/glauth.js",
)
}
func assetsGlauthJs() (*asset, error) {
bytes, err := assetsGlauthJsBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "assets/glauth.js", size: 1386, mode: os.FileMode(436), modTime: time.Unix(1526347681, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _assetsIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x57\x6d\x6f\xdb\x36\x10\xfe\x9e\x5f\x71\xd1\xbe\x4e\x62\xed\xae\x5b\x17\xc8\x06\xba\x34\x5b\x03\xb4\x6b\x90\x76\xd8\x86\xa2\x28\x68\xf1\x6c\xd1\xa1\x48\x85\x77\x72\x62\x08\xfa\xef\x03\x2d\xbf\xc9\x73\x12\xc7\xd8\xbe\xd8\x22\xf9\xdc\xcb\xf3\xf0\x8e\xa2\xd2\xd3\xb7\x1f\xcf\x3f\xff\x7d\x75\x01\x39\x17\x66\x78\x92\x86\x3f\x30\xd2\x4e\x06\x11\xda\x68\x78\x02\x90\xe6\x28\x55\x78\x00\x48\x0b\x64\x09\x59\x2e\x3d\x21\x0f\xa2\x8a\xc7\xf1\xeb\x68\x7b\x29\x67\x2e\x63\xbc\xad\xf4\x6c\x10\xfd\x15\xff\xf1\x26\x3e\x77\x45\x29\x59\x8f\x0c\x46\x90\x39\xcb\x68\x79\x10\x5d\x5e\x0c\x50\x4d\xb0\x63\x69\x65\x81\x83\x68\xa6\xf1\xae\x74\x9e\xb7\xc0\x77\x5a\x71\x3e\x50\x38\xd3\x19\xc6\x8b\xc1\xf7\xa0\xad\x66\x2d\x4d\x4c\x99\x34\x38\xe8\xed\x71\xa4\x90\x32\xaf\x4b\xd6\xce\x6e\xf9\xda\x03\x94\x15\xe7\xce\x77\x31\x2d\x88\x35\x1b\x1c\xfe\xf6\xfe\x4d\xc5\x39\x7c\x62\xc9\x15\xa5\xa2\x9d\x5c\x22\x8c\xb6\x37\xe0\xd1\x0c\x22\xe2\xb9\x41\xca\x11\x39\x82\xdc\xe3\x78\x10\x05\x25\xe8\x4c\x88\x42\xde\x67\xca\x26\x23\xe7\x98\xd8\xcb\x32\x0c\x32\x57\x88\xf5\x84\x78\x99\xf4\x93\x17\x22\x23\xda\xcc\x25\x85\xb6\x49\x46\xb4\xca\xf7\x7f\x0b\x14\x73\x8e\x05\x3e\x27\x9c\x24\x42\x26\x31\x31\x41\xb8\xa5\x51\x6b\x75\x1a\xc7\xf0\xee\xf3\x87\xf7\xaf\x80\x72\x5d\x80\xb4\x0a\xae\x91\x4a\x67\x55\x32\x25\xb8\xbc\x78\x0d\x54\x95\x61\x73\xc1\x8d\x97\x40\x34\x58\xa0\x65\x5a\x80\x0b\x54\x5a\xc2\x6d\x85\x5e\x23\x41\x1c\x0f\xd7\x6e\xbf\xe8\x31\x18\x86\xcb\x0b\xf8\xf9\x6b\x3b\x0b\x90\xb6\xfb\x0b\xe4\xb3\x8d\x06\x8e\x28\x59\xea\x10\xa8\x87\x5a\x7e\x45\xb9\x9e\x89\x97\xc9\x4f\x49\x7f\x33\x5e\x10\x9e\x52\x34\x4c\x45\xeb\xe6\x39\x5e\x7d\x4b\x4a\xf4\x92\x1f\x92\xfe\x6a\xf4\x80\xc7\xf4\xf4\x0b\x5a\xa5\xc7\x5f\x5b\x3a\xa9\x68\x7b\x29\x3c\x8e\x9c\x9a\x83\x77\x26\x14\xab\xcb\xaa\xa0\xc3\x5a\x4a\xa5\x67\x90\x19\x49\x34\x88\x42\x5d\x4a\x6d\xd1\xc3\x62\xab\x62\xca\xdd\x5d\x26\x09\xa3\xa5\x6d\x21\xb5\x5d\xd9\x2d\x37\xe1\x83\xd4\x16\xa6\x55\x31\x72\xec\x9d\x85\xb1\xf3\x20\xa1\xf4\xba\x90\x7e\x0e\x85\xf4\x37\xc8\xda\x4e\xa0\x40\x22\x39\x41\x70\x1e\x32\x69\x0c\xb0\x03\x99\x85\x86\x59\x6b\xdf\xcd\x64\xed\x31\x5a\xad\xae\x11\x5a\x85\x52\x91\x4c\xa5\xb4\x68\xa2\x95\xc5\x62\x04\x8b\xdf\x58\xe1\x58\x56\x86\xa1\xac\x8c\x89\xbd\x9e\xe4\xdc\x75\x23\x94\x9e\x6d\x26\xd2\xbc\xb7\xec\xbc\x54\xe4\xbd\xad\xf9\x72\xf8\x39\xd7\x04\xec\x9c\x01\x4d\xa0\x2d\x28\x9c\xa1\x71\x65\xd0\x0f\x62\xb0\x8e\xe1\xb6\xd2\x8c\xe0\x51\xaa\xf9\x82\x7c\xe9\x9d\xaa\x16\xcc\x4e\x53\x51\x6e\xb4\x6a\x43\xee\x61\xea\xdd\xdd\x56\x72\xdd\xdd\x30\xf1\x3d\xc5\xbd\x3e\x84\x27\x2a\xe2\x1f\xbb\x2c\xf2\xfe\x70\xa1\x43\x2a\xf2\xfe\xc3\x2a\x45\x4f\x48\xba\xa3\x45\x27\xcf\x67\xe7\x33\x92\xd9\x0d\x5a\x05\x84\x7e\x86\xfe\x91\xcc\xda\xf5\xa3\x72\xfb\x77\xa2\xed\x73\x5b\x8f\x62\x53\xc3\xa1\xb2\x4e\xb6\x1b\xad\x5b\x39\x31\x63\x51\x1a\xc9\x18\x01\xcf\x4b\x1c\x44\x8c\xf7\x2c\xee\xe3\x5c\x5a\x65\x70\x24\x3d\x6d\x10\xfb\x37\xa8\x75\x13\x7a\xab\x93\x76\x08\x58\x4a\xbb\x42\x15\xc8\x5e\x67\xd1\xb0\xf4\xee\x7e\x9e\x04\xda\xda\xd9\xb3\x54\x04\xcc\x10\xea\xba\x33\xdf\x34\xe9\xc8\xef\xa5\xbc\xe9\xf5\xfd\x9c\x8e\xa1\xf3\x70\x9e\xdf\xc6\x7e\xf1\x8e\x52\xc9\x48\x5b\xf5\xcd\xe3\x2d\xed\xa6\xbc\x07\xd2\xc9\xfe\x60\xe7\x54\x65\x19\x12\xe1\x53\x11\xd6\xb8\xe3\xc2\xa0\xf7\xce\x3f\x15\xa3\x05\x3d\x3b\x00\xa1\xf4\x59\xfe\x84\x4c\x5b\xa0\x63\x03\x1c\x20\xd5\x2e\xf2\xd8\x50\x4f\xc9\xd5\x81\x3d\x3b\x48\x66\xdc\xa3\x2c\xda\xf5\x2d\xb7\x8f\x95\x7f\x7b\x98\x1c\xd3\x00\x75\xfd\x1d\xca\x2c\x87\x36\xfa\xf2\xf4\x4a\x96\x0e\x9b\xe6\xe4\xd1\x86\xae\xeb\x4f\x59\x78\x4d\x36\xcd\x99\x10\x75\xfd\xce\x11\x87\x6b\x5e\xd3\x9c\xd5\xf5\x95\xf3\xdc\x34\x1b\x7a\x57\xda\x4e\x9a\x06\xea\xba\xbd\xdb\x35\x0d\x6c\x09\x56\xd7\x22\x64\xb1\x8c\xf7\x08\x53\x96\xfa\xa8\x63\x2b\xe5\x70\x48\x6d\x8e\x95\x15\x6d\xce\x75\x87\x25\xfb\x15\xc7\xdd\x03\x8d\xd5\x6a\xc5\x95\x50\xd7\x1f\xcb\xa6\x09\x02\x84\xff\x54\xb0\x3a\x0c\xfd\x8b\xb6\xea\xed\xef\xcf\xb1\xf8\xb4\x28\xb2\x6b\xbc\x4d\x7e\xd5\x86\xd1\x3f\x61\x1b\x4c\xae\x91\x2a\xc3\xe7\x4e\xe1\x01\xe0\x95\xff\x60\x42\x07\xe0\xcf\x8d\x46\xcb\x97\x57\x07\x40\x95\x64\xbc\x46\x23\x59\xcf\x10\xfe\xcc\xd1\xee\xda\xa4\x82\xfd\xf6\x96\x6c\xd7\x40\x77\xcb\x1e\xa8\x88\xce\xbd\x51\x4e\xe5\x7d\x32\x71\x6e\x62\x50\x96\x9a\x16\x97\xc7\x30\x27\x8c\x1e\x91\x98\x86\x9b\xee\x5c\xf4\x92\x5e\x2f\xe9\x2d\x47\x0f\xdd\x22\xf7\x79\x3f\xf4\xbe\x3f\xdd\xfd\xae\x38\xd0\x7f\xa6\xec\x94\x42\xd3\x57\x6a\x6c\xa4\xc7\x9d\xf4\x37\xb5\x9d\x4c\x49\xf4\x93\x17\xc9\x8b\xed\xb9\xff\x2e\x50\xe1\xc2\xd5\xae\x0d\xf2\x3a\x79\xb9\x1c\xc7\x77\x9a\xf3\xd8\xb8\xf0\x01\x78\x50\xb4\xee\x77\xcb\x2e\x38\x15\xed\xd6\xa6\xa2\xfd\x0c\xfe\x27\x00\x00\xff\xff\x08\x9c\x44\x70\x17\x0f\x00\x00")
func assetsIndexHtmlBytes() ([]byte, error) {
return bindataRead(
_assetsIndexHtml,
"assets/index.html",
)
}
func assetsIndexHtml() (*asset, error) {
bytes, err := assetsIndexHtmlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "assets/index.html", size: 3863, mode: os.FileMode(436), modTime: time.Unix(1524616076, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// 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) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"assets/glauth.css": assetsGlauthCss,
"assets/glauth.js": assetsGlauthJs,
"assets/index.html": assetsIndexHtml,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"assets": {nil, map[string]*bintree{
"glauth.css": {assetsGlauthCss, map[string]*bintree{}},
"glauth.js": {assetsGlauthJs, map[string]*bintree{}},
"index.html": {assetsIndexHtml, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}
-358
View File
@@ -1,358 +0,0 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/GeertJohan/yubigo"
"github.com/nmcclain/ldap"
"github.com/pquerna/otp/totp"
"net"
"sort"
"strings"
)
type configHandler struct {
cfg *config
yubikeyAuth *yubigo.YubiAuth
}
func newConfigHandler(cfg *config, yubikeyAuth *yubigo.YubiAuth) Backend {
handler := configHandler{
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)
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) {
log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
// log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, baseDN))
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.", userName))
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.", groupName))
return ldap.LDAPResultInvalidCredentials, nil
}
// validate group membership
if user.PrimaryGroup != group.UnixID {
log.Warning(fmt.Sprintf("Bind Error: User %s primary group is not %s.", userName, groupName))
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
}
}
}
}
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)
}
}
if !validotp {
log.Warning(fmt.Sprintf("Bind Error: invalid token as %s from %s", bindDN, conn.RemoteAddr().String()))
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(append(u.OtherGroups, u.PrimaryGroup))})
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
}
}
}
}
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 {
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,ou=groups,%s", g.Name, 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 ""
}
+369 -162
View File
@@ -1,17 +1,27 @@
package main
import (
"expvar"
"fmt"
"github.com/BurntSushi/toml"
"github.com/GeertJohan/yubigo"
"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"
"time"
"github.com/GeertJohan/yubigo"
"github.com/davecgh/go-spew/spew"
docopt "github.com/docopt/docopt-go"
"github.com/fsnotify/fsnotify"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/frontend"
gologgingr "github.com/glauth/glauth/pkg/gologgingr"
"github.com/glauth/glauth/pkg/server"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/hydronica/toml"
"github.com/jinzhu/copier"
logging "github.com/op/go-logging"
"gopkg.in/amz.v3/aws"
"gopkg.in/amz.v3/s3"
// "github.com/davecgh/go-spew/spew"
)
// Set with buildtime vars
@@ -27,7 +37,7 @@ const programName = "glauth"
var usage = `glauth: securely expose your LDAP for external auth
Usage:
glauth [options] -c <file|s3url>
glauth [options] -c <file|s3 url>
glauth -h --help
glauth --version
@@ -36,82 +46,22 @@ Options:
-K <aws_key_id> AWS Key ID.
-S <aws_secret_key> AWS Secret Key.
-r <aws_region> AWS Region [default: us-east-1].
--ldap <address> Listen address for the LDAP server.
--ldaps <address> Listen address for the LDAPS server.
--ldaps-cert <cert-file> Path to cert file for the LDAPS server.
--ldaps-key <key-file> Path to key file for the LDAPS server.
-h, --help Show this screen.
--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 logr.Logger
args map[string]interface{}
stderr *logging.LogBackend
yubiAuth *yubigo.YubiAuth
// interface for backend handler
type Backend interface {
ldap.Binder
ldap.Searcher
ldap.Closer
}
// config file
type configBackend struct {
BaseDN string
Datastore string
Insecure bool // For LDAP backend only
Servers []string // For LDAP backend only
}
type configFrontend struct {
AllowedBaseDNs []string // For LDAP backend only
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
OTPSecret string
Yubikey string
Disabled bool
UnixID int
Mail string
LoginShell string
GivenName string
SN string
Homedir string
}
type configGroup struct {
Name string
UnixID int
IncludeGroups []int
}
type config struct {
API configAPI
Backend configBackend
Debug bool
YubikeyClientID string
YubikeySecret string
Frontend configFrontend
Groups []configGroup
Syslog bool
Users []configUser
ConfigFile string
AwsAccessKeyId string
AwsSecretAccessKey string
AwsRegion string
}
var log = logging.MustGetLogger(programName)
activeConfig = &config.Config{}
)
// Reads builtime vars and returns a full string containing info about
// the currently running version of the software. Primarily used by the
@@ -156,82 +106,148 @@ func getVersionString() string {
}
func main() {
stderr := initLogging()
log.Debug("AP start")
stderr = initLogging()
log.V(6).Info("AP start")
cfg, err := doConfig()
if err != nil {
log.Fatalf("Configuration file error: %s", err.Error())
if err := parseArgs(); err != nil {
log.Error(err, "Could not parse command-line arguments")
os.Exit(1)
}
if cfg.Syslog {
enableSyslog(stderr)
if err := doConfig(); err != nil {
log.Error(err, "Configuration file error")
os.Exit(1)
}
// stats
stats_general.Set("version", stringer(LastGitTag))
// web API
if cfg.API.Enabled {
log.Debug("Web API enabled")
go RunAPI(cfg)
}
yubiAuth := (*yubigo.YubiAuth)(nil)
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
yubiAuth, err = yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
if err != nil {
log.Fatalf("Yubikey Auth failed")
}
}
// configure the backend
s := ldap.NewServer()
s.EnforceLDAP = true
var handler Backend
switch cfg.Backend.Datastore {
case "ldap":
handler = newLdapHandler(cfg)
case "config":
handler = newConfigHandler(cfg, yubiAuth)
default:
log.Fatalf("Unsupported backend %s - must be 'config' or 'ldap'.", cfg.Backend.Datastore)
}
log.Notice(fmt.Sprintf("Using %s backend", cfg.Backend.Datastore))
s.BindFunc("", handler)
s.SearchFunc("", handler)
s.CloseFunc("", handler)
// 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())
}
}
log.Critical("AP exit")
startService()
}
// doConfig reads the cli flags and config file
func doConfig() (*config, error) {
cfg := config{}
// setup defaults
cfg.Frontend.TLS = true
func startService() {
// stats
stats.General.Set("version", stats.Stringer(LastGitTag))
// parse the command-line args
args, err := docopt.Parse(usage, nil, true, getVersionString(), false)
if err != nil {
return &cfg, err
// web API
if activeConfig.API.Enabled {
log.V(6).Info("Web API enabled")
go frontend.RunAPI(
frontend.Logger(log),
frontend.Config(&activeConfig.API),
)
}
startConfigWatcher()
s, err := server.NewServer(
server.Logger(log),
server.Config(activeConfig),
)
if err != nil {
log.Error(err, "Could not create server")
os.Exit(1)
}
if activeConfig.LDAP.Enabled {
// Don't block if also starting a LDAPS server afterwards
shouldBlock := !activeConfig.LDAPS.Enabled
if shouldBlock {
if err := s.ListenAndServe(); err != nil {
log.Error(err, "Could not start LDAP server")
os.Exit(1)
}
} else {
go func() {
if err := s.ListenAndServe(); err != nil {
log.Error(err, "Could not start LDAP server")
os.Exit(1)
}
}()
}
}
if activeConfig.LDAPS.Enabled {
// Always block here
if err := s.ListenAndServeTLS(); err != nil {
log.Error(err, "Could not start LDAPS server")
os.Exit(1)
}
}
log.V(0).Info("AP exit")
os.Exit(1)
}
func startConfigWatcher() {
configFileLocation := getConfigLocation()
if !activeConfig.WatchConfig || strings.HasPrefix(configFileLocation, "s3://") {
return
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Error(err, "Could not start config-watcher")
return
}
ticker := time.NewTicker(1 * time.Second)
go func() {
isChanged, isRemoved := false, false
for {
select {
case event := <-watcher.Events:
log.V(6).Info("watcher got event", "e", event.Op.String())
if event.Op&fsnotify.Write == fsnotify.Write {
isChanged = true
} else if event.Op&fsnotify.Remove == fsnotify.Remove { // vim edit file with rename/remove
isChanged, isRemoved = true, true
}
case err := <-watcher.Errors:
log.Error(err, "Error!")
case <-ticker.C:
// wakeup, try finding removed config
}
if _, err := os.Stat(configFileLocation); !os.IsNotExist(err) && (isRemoved || isChanged) {
if isRemoved {
log.V(6).Info("rewatching config", "file", configFileLocation)
watcher.Add(configFileLocation) // overwrite
isChanged, isRemoved = true, false
}
if isChanged {
if err := doConfig(); err != nil {
log.V(2).Info("Could not reload config. Holding on to old config", "error", err.Error())
} else {
log.V(3).Info("Config was reloaded")
}
isChanged = false
}
}
}
}()
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
// 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"])
}
@@ -247,12 +263,15 @@ 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)
}
b := s3.New(auth, region).Bucket(parts[0])
b, err := s3.New(auth, region).Bucket(parts[0])
if err != nil {
return &cfg, err
}
tomlData, err := b.Get(parts[1])
if err != nil {
return &cfg, err
@@ -261,30 +280,215 @@ 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")
// Backward Compability
if cfg.Backend.Datastore != "" {
if cfg.Backends != nil {
return &cfg, fmt.Errorf("You cannot specify both [Backend] and [[Backends]] directives in the same configuration ")
} else {
cfg.Backends = append(cfg.Backends, cfg.Backend)
}
}
switch cfg.Backend.Datastore {
case "":
cfg.Backend.Datastore = "config"
case "config":
case "ldap":
default:
return &cfg, fmt.Errorf("Invalid backend %s - must be 'config' or 'ldap'.", cfg.Backend.Datastore)
spew.Dump(cfg.Backends)
// Patch with default values where not specified
for i := range cfg.Backends {
if cfg.Backends[i].NameFormat == "" {
cfg.Backends[i].NameFormat = "cn"
}
if cfg.Backends[i].GroupFormat == "" {
cfg.Backends[i].GroupFormat = "ou"
}
if cfg.Backends[i].SSHKeyAttr == "" {
cfg.Backends[i].SSHKeyAttr = "sshPublicKey"
}
}
//
return &cfg, nil
}
func handleArgs(cfg config.Config) (*config.Config, error) {
// LDAP flags
if ldap, ok := args["--ldap"].(string); ok && ldap != "" {
cfg.LDAP.Enabled = true
cfg.LDAP.Listen = ldap
}
// LDAPS flags
if ldaps, ok := args["--ldaps"].(string); ok && ldaps != "" {
cfg.LDAPS.Enabled = true
cfg.LDAPS.Listen = ldaps
}
if ldapsCert, ok := args["--ldaps-cert"].(string); ok && ldapsCert != "" {
cfg.LDAPS.Cert = ldapsCert
}
if ldapsKey, ok := args["--ldaps-key"].(string); ok && ldapsKey != "" {
cfg.LDAPS.Key = ldapsKey
}
return &cfg, nil
}
func handleLegacyConfig(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.V(2).Info("Config [frontend] is deprecated - please move to [ldap] and [ldaps] as-per documentation")
cfg.LDAP.Enabled = !cfg.Frontend.TLS
cfg.LDAPS.Enabled = cfg.Frontend.TLS
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
}
}
return &cfg, nil
}
func validateConfig(cfg config.Config) (*config.Config, error) {
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")
}
}
//spew.Dump(cfg)
for i := range cfg.Backends {
switch cfg.Backends[i].Datastore {
case "":
cfg.Backends[i].Datastore = "config"
case "config":
case "ldap":
case "owncloud":
case "plugin":
default:
return &cfg, fmt.Errorf("invalid backend %s - must be 'config', 'ldap', 'owncloud' or 'plugin'", cfg.Backends[i].Datastore)
}
}
// TODO: remove after deprecating UnixID on User and Group
for _, user := range cfg.Users {
if user.UnixID != 0 {
user.UIDNumber = user.UnixID
log.V(2).Info(fmt.Sprintf("User '%s': 'unixid' is deprecated - please move to 'uidnumber' as per documentation", user.Name))
}
}
for _, group := range cfg.Groups {
if group.UnixID != 0 {
group.GIDNumber = group.UnixID
log.V(2).Info(fmt.Sprintf("Group '%s': 'unixid' is deprecated - please move to 'gidnumber' as per documentation", group.Name))
}
}
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 parsed flags
cfg, err = handleArgs(*cfg)
if err != nil {
return err
}
// Handle parsing of legacy [frontend] section into [ldap] and/or [ldaps] sections
cfg, err = handleLegacyConfig(*cfg)
if err != nil {
return err
}
cfg, err = validateConfig(*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.V(6).Info("Debugging enabled")
}
if activeConfig.Syslog {
enableSyslog(stderr)
}
return nil
}
// initLogging sets up logging to stderr
func initLogging() *logging.LogBackend {
l := logging.MustGetLogger(programName)
l.ExtraCalldepth = 2 // add extra call depth for the logr wrapper
log = gologgingr.New(
gologgingr.Logger(l),
)
gologgingr.SetVerbosity(10) // do not filter by verbosity. glauth uses the go-logging lib to filter the levels
format := "%{color}%{time:15:04:05.000000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
logging.SetBackend(logBackend)
logging.SetLevel(logging.NOTICE, programName)
logging.SetFormatter(logging.MustStringFormatter(format))
@@ -297,8 +501,11 @@ func enableSyslog(stderrBackend *logging.LogBackend) {
logging.SetFormatter(logging.MustStringFormatter(format))
syslogBackend, err := logging.NewSyslogBackend("")
if err != nil {
log.Fatal(err)
log.Error(err, "Could not create new syslog backend")
os.Exit(1)
}
logging.SetBackend(stderrBackend, syslogBackend)
log.Debug("Syslog enabled")
log.V(6).Info("Syslog enabled")
}
+210 -19
View File
@@ -1,32 +1,223 @@
package main
// Just the beginning of testing - more to be added
import (
"os/exec"
"strings"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestGetVersionString(t *testing.T) {
type testEnv struct {
checkanonymousrootDSE bool
checkTOTP bool
checkbindUPN bool
svcdn string
svcdnnogroup string
otpdn string
expectedinfo string
expectedaccount string
expectedfirstaccount string
expectedgroup string
}
// Setup config and check output
LastGitTag = "7"
BuildTime = "20180616_042045Z"
GitCommit = "a9837f3112fc049fd1e9faca6f56d8be1bec91bd"
GitClean = "1"
GitBranch = "dev"
GitTagIsCommit = "0"
func TestIntegerStuff(t *testing.T) {
versionOutput := getVersionString()
if versionOutput != "7" {
t.Errorf("Finish writing this test, output was: %s", versionOutput)
Convey("Testing sample-simple local file-based LDAP server", t, func() {
svc := startSvc(SD, "bin/glauth", "-c", "sample-simple.cfg")
batteryOfTests(
t,
svc, testEnv{
checkanonymousrootDSE: true,
checkTOTP: true,
checkbindUPN: true,
expectedinfo: "supportedLDAPVersion: 3",
svcdn: "cn=serviceuser,ou=svcaccts,dc=glauth,dc=com",
svcdnnogroup: "cn=serviceuser,dc=glauth,dc=com",
otpdn: "cn=otpuser,ou=superheros,dc=glauth,dc=com",
expectedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedfirstaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedgroup: "dn: cn=superheros,ou=groups,dc=glauth,dc=com",
})
stopSvc(svc)
})
matchingLibrary := doRunGetFirst(RD, "ls", "bin/sqlite.so")
if matchingLibrary == "bin/sqlite.so" {
Convey("Testing sample-database LDAP server", t, func() {
svc := startSvc(SD, "bin/glauth", "-c", "pkg/plugins/sample-database.cfg")
batteryOfTests(
t,
svc, testEnv{
checkanonymousrootDSE: true,
checkTOTP: false,
checkbindUPN: true,
expectedinfo: "supportedLDAPVersion: 3",
svcdn: "cn=serviceuser,ou=svcaccts,dc=glauth,dc=com",
svcdnnogroup: "cn=serviceuser,dc=glauth,dc=com",
otpdn: "cn=otpuser,ou=superheros,dc=glauth,dc=com",
expectedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedfirstaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedgroup: "dn: cn=superheros,ou=groups,dc=glauth,dc=com",
})
stopSvc(svc)
})
}
// TODO: write these cases
matchingContainers := doRunGetFirst(RD, "sh", "-c", "docker ps | grep openldap-service | wc -l")
if matchingContainers == "1" {
Convey("Testing sample-simple local LDAP server", t, func() {
svc := startSvc(SD, "bin/glauth", "-c", "sample-ldap-injection.cfg")
batteryOfTests(
t,
svc, testEnv{
checkanonymousrootDSE: false,
checkTOTP: true,
checkbindUPN: false,
expectedinfo: "objectClass: top",
svcdn: "cn=serviceuser,cn=svcaccts,ou=users,dc=glauth,dc=com",
svcdnnogroup: "", // ignore
otpdn: "cn=otpuser,cn=superheros,ou=users,dc=glauth,dc=com",
expectedaccount: "dn: cn=hackers,cn=superheros,ou=users,dc=glauth,dc=com",
expectedfirstaccount: "dn: cn=johndoe,cn=superheros,ou=users,dc=glauth,dc=com",
expectedgroup: "dn: cn=superheros,ou=users,dc=glauth,dc=com",
})
stopSvc(svc)
})
}
}
// 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
func batteryOfTests(t *testing.T, svc *exec.Cmd, env testEnv) {
Convey("When searching for the 'hackers' user", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should find them in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
})
})
if env.svcdnnogroup != "" {
Convey("When searching for the 'hackers' user without binding with a group", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdnnogroup, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should find them in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
})
})
}
if env.checkbindUPN {
Convey("When searching for the 'hackers' user after binding using the account's UPN", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", "serviceuser@example.com", "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should find them in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
})
})
}
Convey("When querying the root SDE", func() {
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-s", "base", "(objectclass=*)")
Convey("We should get some meta information", func() {
So(out, ShouldEqual, env.expectedinfo)
})
})
if env.checkanonymousrootDSE {
Convey("When querying the root SDE anonymously without authorizing in config file", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-x", "-s", "base", "(objectclass=*)")
Convey("We should get error 50", func() {
So(out, ShouldEqual, "exit status 50")
})
})
}
Convey("When enumerating posix groups", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(objectclass=posixgroup)")
Convey("We should get a list starting with the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedgroup)
})
})
Convey("When searching for members of the 'superheros' group", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(memberOf=cn=superheros,ou=groups,dc=glauth,dc=com)")
Convey("We should get a list starting with the 'hackers' user", func() {
So(out, ShouldEqual, env.expectedfirstaccount)
})
})
Convey("When performing a complex search for members of 'superheros' group", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(&(objectClass=*)(memberOf=cn=superheros,ou=groups,dc=glauth,dc=com))")
Convey("We should get a list starting with the 'hackers' user", func() {
So(out, ShouldEqual, env.expectedfirstaccount)
})
})
if env.checkTOTP {
Convey("When searching for the 'hacker' user using a TOTP-enabled account", func() {
otpvalue := doRunGetFirst(RD, "oathtool", "--totp", "-b", "-d", "6", "3hnvnk4ycv44glzigd6s25j4dougs3rk")
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret"+otpvalue, "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should find them in in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
})
})
Convey("When searching for the 'hacker' user using a TOTP-enabled account and no value", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should get 'Invalid credentials(49)'", func() {
So(out, ShouldEqual, "exit status 49")
})
})
Convey("When searching for the 'hacker' user using a TOTP-enabled account and the wrong value", func() {
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret123456", "-x", "-bdc=glauth,dc=com", "cn=hackers")
Convey("We should get 'Invalid credentials(49)'", func() {
So(out, ShouldEqual, "exit status 49")
})
})
}
}
// -----=============================================================================----
const SD = 3 // Start Delay
const RD = 2 // Response Delay
func startSvc(delay time.Duration, name string, arg ...string) *exec.Cmd {
cmd := exec.Command(name, arg...)
cmd.Start()
if delay != 0 {
time.Sleep(time.Second * delay)
}
return cmd
}
func stopSvc(svc *exec.Cmd) {
svc.Process.Kill()
}
func doRunGetFirst(delay time.Duration, name string, arg ...string) string {
out := strings.SplitN(doRun(delay, name, arg...), "\n", 2)
if out == nil || len(out) < 1 {
return "*fail*"
}
return out[0]
}
func doRunGetSecond(delay time.Duration, name string, arg ...string) string {
out := strings.SplitN(doRun(delay, name, arg...), "\n", 3)
if out == nil || len(out) < 2 {
return "*fail*"
}
return out[1]
}
func doRun(delay time.Duration, name string, arg ...string) string {
out, err := exec.Command(name, arg...).Output()
if err != nil {
return err.Error()
}
if delay != 0 {
time.Sleep(time.Second * delay)
}
return strings.TrimSpace(string(out))
}
+28
View File
@@ -0,0 +1,28 @@
module github.com/glauth/glauth
go 1.15
require (
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
github.com/boombuler/barcode v1.0.1
github.com/davecgh/go-spew v1.1.1
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/fsnotify/fsnotify v1.4.9
github.com/go-logr/logr v0.4.0
github.com/go-sql-driver/mysql v1.5.0
github.com/hydronica/toml v0.4.2
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/jteeuwen/go-bindata v3.0.7+incompatible
github.com/kr/pretty v0.2.1
github.com/lib/pq v1.10.0
github.com/mattn/go-sqlite3 v1.14.6
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.3.0
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
)
+88
View File
@@ -0,0 +1,88 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
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/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hydronica/toml v0.4.2 h1:BY+iAvyl2u1BlfgkJQzqc1UXDGLWfpMmOWuqJrwAVDU=
github.com/hydronica/toml v0.4.2/go.mod h1:c7QhbYq3Wp9SlOWuG7MAieKUyXP2P/hXhy/YqWfbS/4=
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/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
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/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba h1:DO8NFYdcRv1dnyAINJIBm6Bw2XibtLvQniNFGzf2W8E=
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba/go.mod h1:4S0XndRL8HNOaQBfdViJ2F/GPCgL524xlXRuXFH12/U=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc h1:ejaC8rvIvCWmsaFrvmGOxhBuMxxhBB1xRshuM98XQ7M=
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc/go.mod h1:tso14hwzqX4VbnWTNsxiL0DvMb2OwbGISFA7jDibdWc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b h1:tdSyAzD5FHJmZAZ13d5WexAo7YJkiyGphhzFfInMnDw=
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b/go.mod h1:cE0dCGx2UfBTjLFlzEx4EXJUmoX6BXBoX9GjKOvqha4=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-294
View File
@@ -1,294 +0,0 @@
package main
import (
"crypto/sha256"
"crypto/tls"
"encoding/json"
"fmt"
"github.com/kr/pretty"
"github.com/nmcclain/ldap"
"net"
"net/url"
"strconv"
"strings"
"sync"
"time"
)
type ldapHandler struct {
sessions map[string]ldapSession
servers []ldapBackend
lock sync.Mutex // for sessions and servers
doPing chan bool
cfg *config
}
type ldapSession struct {
id string
c net.Conn
ldap *ldap.Conn
}
type ldapBackendStatus int
const (
Down ldapBackendStatus = iota
Up
)
type ldapBackend struct {
Scheme string
Hostname string
Port int
Status ldapBackendStatus
Ping time.Duration
}
func newLdapHandler(cfg *config) Backend {
handler := ldapHandler{ // set non-zero-value defaults here
sessions: make(map[string]ldapSession),
doPing: make(chan bool),
cfg: cfg,
}
// parse LDAP URLs
for _, ldapurl := range cfg.Backend.Servers {
l, err := parseURL(ldapurl)
if err != nil {
log.Fatal(err)
}
handler.servers = append(handler.servers, l)
}
// test server connectivity before listening, then keep it updated
handler.monitorServers()
return handler
}
//
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)
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())
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())
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 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)
s, err := h.getSession(conn)
if err != nil {
stats_frontend.Add("search_ldapSession_errors", 1)
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, nil
}
search := ldap.NewSearchRequest(
searchReq.BaseDN,
searchReq.Scope,
searchReq.DerefAliases,
searchReq.SizeLimit,
searchReq.TimeLimit,
searchReq.TypesOnly,
searchReq.Filter,
searchReq.Attributes,
searchReq.Controls,
)
log.Debug("Search req to backend: %# v", pretty.Formatter(search))
sr, err := s.ldap.Search(search)
log.Debug("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))
if err != nil {
e := err.(*ldap.Error)
log.Debug("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))
return ssr, nil
}
func (h ldapHandler) 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
}
// monitorServers tests server connectivity before listening, then keeps it updated
func (h *ldapHandler) monitorServers() {
err := h.ping()
if err != nil {
log.Fatal(err)
}
go func() {
for {
select {
case <-h.doPing:
log.Notice("doPing requested due to server failure")
err = h.ping()
if err != nil {
log.Fatal(err)
}
case <-time.NewTimer(60 * time.Second).C:
log.Debug("doPing after timeout")
err = h.ping()
if err != nil {
log.Fatal(err)
}
}
}
}()
}
//
func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
id := connID(conn)
h.lock.Lock()
s, ok := h.sessions[id] // use server connection if it exists
h.lock.Unlock()
if !ok { // open a new server connection if not
var l *ldap.Conn
server, err := h.getBestServer() // pick the best server
if err != nil {
return ldapSession{}, err
}
dest := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
if server.Scheme == "ldaps" {
tlsCfg := &tls.Config{}
if h.cfg.Backend.Insecure {
tlsCfg.InsecureSkipVerify = true
}
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
} else if server.Scheme == "ldap" {
l, err = ldap.Dial("tcp", dest)
}
if err != nil {
select {
case h.doPing <- true: // non-blocking send
default:
}
return ldapSession{}, err
}
s = ldapSession{id: id, c: conn, ldap: l}
h.lock.Lock()
h.sessions[s.id] = s
h.lock.Unlock()
}
return s, nil
}
//
func (h ldapHandler) ping() error {
healthy := false
for k, s := range h.servers {
var l *ldap.Conn
var err error
dest := fmt.Sprintf("%s:%d", s.Hostname, s.Port)
start := time.Now()
if h.servers[0].Scheme == "ldaps" {
tlsCfg := &tls.Config{}
if h.cfg.Backend.Insecure {
tlsCfg.InsecureSkipVerify = true
}
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
} else if h.servers[0].Scheme == "ldap" {
l, err = ldap.Dial("tcp", dest)
}
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.servers[k].Ping = 0
h.servers[k].Status = Down
} else {
healthy = true
h.servers[k].Ping = elapsed
h.servers[k].Status = Up
l.Close() // prank caller
}
h.lock.Unlock()
}
log.Debug("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()))
}
stats_backend.Set("servers", stringer(string(b)))
if healthy == false {
return fmt.Errorf("No healthy servers")
}
return nil
}
//
func (h ldapHandler) getBestServer() (ldapBackend, error) {
favorite := ldapBackend{}
forever, err := time.ParseDuration("30m")
if err != nil {
return ldapBackend{}, err
}
bestping := forever
for _, s := range h.servers {
if s.Status == Up && s.Ping < bestping {
favorite = s
bestping = s.Ping
}
}
if bestping == forever {
return ldapBackend{}, fmt.Errorf("No healthy servers found")
}
log.Debug("Best server: %# v", pretty.Formatter(favorite))
return favorite, nil
}
// helper functions
func connID(conn net.Conn) string {
h := sha256.New()
h.Write([]byte(conn.LocalAddr().String() + conn.RemoteAddr().String()))
sha := fmt.Sprintf("% x", h.Sum(nil))
return string(sha)
}
func parseURL(ldapurl string) (ldapBackend, error) {
u, err := url.Parse(ldapurl)
if err != nil {
return ldapBackend{}, err
}
var port int
if u.Scheme == "ldaps" {
port = 636
} else if u.Scheme == "ldap" {
port = 389
} else {
return ldapBackend{}, fmt.Errorf("Unknown LDAP scheme: %s", u.Scheme)
}
parts := strings.Split(u.Host, ":")
hostname := parts[0]
if len(parts) > 1 {
port, err = strconv.Atoi(parts[1])
if err != nil {
return ldapBackend{}, err
}
}
return ldapBackend{Scheme: u.Scheme, Hostname: hostname, Port: port}, nil
}
+23
View File
@@ -0,0 +1,23 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 e5ffe9fd
dn: cn=config
objectClass: olcGlobal
cn: config
olcArgsFile: /var/run/slapd/slapd.args
olcLogLevel: none
olcPidFile: /var/run/slapd/slapd.pid
olcToolThreads: 1
structuralObjectClass: olcGlobal
entryUUID: 5c21e0e8-7ddc-103b-9ebb-c740e015b81b
creatorsName: cn=config
createTimestamp: 20210720192809Z
olcTLSCipherSuite: SECURE256:+SECURE128:-VERS-TLS-ALL:+VERS-TLS1.2:-RSA:-DHE
-DSS:-CAMELLIA-128-CBC:-CAMELLIA-256-CBC
olcTLSCACertificateFile: /container/service/slapd/assets/certs/ca.crt
olcTLSCertificateFile: /container/service/slapd/assets/certs/ldap.crt
olcTLSCertificateKeyFile: /container/service/slapd/assets/certs/ldap.key
olcTLSDHParamFile: /container/service/slapd/assets/certs/dhparam.pem
olcTLSVerifyClient: demand
entryCSN: 20210812002157.868917Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210812002157Z
@@ -0,0 +1,16 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 2aabc521
dn: cn=module{0}
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
olcModuleLoad: {1}memberof
olcModuleLoad: {2}refint
structuralObjectClass: olcModuleList
entryUUID: 5c2b1fb4-7ddc-103b-9ec3-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.856380Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,12 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 bc37faae
dn: cn=schema
objectClass: olcSchemaConfig
cn: schema
structuralObjectClass: olcSchemaConfig
entryUUID: 5c2570a0-7ddc-103b-9ebe-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.316331Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,249 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 57d21f8c
dn: cn={0}core
objectClass: olcSchemaConfig
cn: {0}core
olcAttributeTypes: {0}( 2.5.4.2 NAME 'knowledgeInformation' DESC 'RFC2256: k
nowledge information' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15{32768} )
olcAttributeTypes: {1}( 2.5.4.4 NAME ( 'sn' 'surname' ) DESC 'RFC2256: last
(family) name(s) for which the entity is known by' SUP name )
olcAttributeTypes: {2}( 2.5.4.5 NAME 'serialNumber' DESC 'RFC2256: serial nu
mber of the entity' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64} )
olcAttributeTypes: {3}( 2.5.4.6 NAME ( 'c' 'countryName' ) DESC 'RFC4519: tw
o-letter ISO-3166 country code' SUP name SYNTAX 1.3.6.1.4.1.1466.115.121.1.
11 SINGLE-VALUE )
olcAttributeTypes: {4}( 2.5.4.7 NAME ( 'l' 'localityName' ) DESC 'RFC2256: l
ocality which this object resides in' SUP name )
olcAttributeTypes: {5}( 2.5.4.8 NAME ( 'st' 'stateOrProvinceName' ) DESC 'RF
C2256: state or province which this object resides in' SUP name )
olcAttributeTypes: {6}( 2.5.4.9 NAME ( 'street' 'streetAddress' ) DESC 'RFC2
256: street address of this object' EQUALITY caseIgnoreMatch SUBSTR caseIgn
oreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {7}( 2.5.4.10 NAME ( 'o' 'organizationName' ) DESC 'RFC22
56: organization this object belongs to' SUP name )
olcAttributeTypes: {8}( 2.5.4.11 NAME ( 'ou' 'organizationalUnitName' ) DESC
'RFC2256: organizational unit this object belongs to' SUP name )
olcAttributeTypes: {9}( 2.5.4.12 NAME 'title' DESC 'RFC2256: title associate
d with the entity' SUP name )
olcAttributeTypes: {10}( 2.5.4.14 NAME 'searchGuide' DESC 'RFC2256: search g
uide, deprecated by enhancedSearchGuide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.
25 )
olcAttributeTypes: {11}( 2.5.4.15 NAME 'businessCategory' DESC 'RFC2256: bus
iness category' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S
YNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {12}( 2.5.4.16 NAME 'postalAddress' DESC 'RFC2256: postal
address' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
olcAttributeTypes: {13}( 2.5.4.17 NAME 'postalCode' DESC 'RFC2256: postal co
de' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.15{40} )
olcAttributeTypes: {14}( 2.5.4.18 NAME 'postOfficeBox' DESC 'RFC2256: Post O
ffice Box' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.15{40} )
olcAttributeTypes: {15}( 2.5.4.19 NAME 'physicalDeliveryOfficeName' DESC 'RF
C2256: Physical Delivery Office Name' EQUALITY caseIgnoreMatch SUBSTR caseI
gnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {16}( 2.5.4.20 NAME 'telephoneNumber' DESC 'RFC2256: Tele
phone Number' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50{32} )
olcAttributeTypes: {17}( 2.5.4.21 NAME 'telexNumber' DESC 'RFC2256: Telex Nu
mber' SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 )
olcAttributeTypes: {18}( 2.5.4.22 NAME 'teletexTerminalIdentifier' DESC 'RFC
2256: Teletex Terminal Identifier' SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 )
olcAttributeTypes: {19}( 2.5.4.23 NAME ( 'facsimileTelephoneNumber' 'fax' )
DESC 'RFC2256: Facsimile (Fax) Telephone Number' SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.22 )
olcAttributeTypes: {20}( 2.5.4.24 NAME 'x121Address' DESC 'RFC2256: X.121 Ad
dress' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.36{15} )
olcAttributeTypes: {21}( 2.5.4.25 NAME 'internationaliSDNNumber' DESC 'RFC22
56: international ISDN number' EQUALITY numericStringMatch SUBSTR numericSt
ringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{16} )
olcAttributeTypes: {22}( 2.5.4.26 NAME 'registeredAddress' DESC 'RFC2256: re
gistered postal address' SUP postalAddress SYNTAX 1.3.6.1.4.1.1466.115.121.
1.41 )
olcAttributeTypes: {23}( 2.5.4.27 NAME 'destinationIndicator' DESC 'RFC2256:
destination indicator' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{128} )
olcAttributeTypes: {24}( 2.5.4.28 NAME 'preferredDeliveryMethod' DESC 'RFC22
56: preferred delivery method' SYNTAX 1.3.6.1.4.1.1466.115.121.1.14 SINGLE-
VALUE )
olcAttributeTypes: {25}( 2.5.4.29 NAME 'presentationAddress' DESC 'RFC2256:
presentation address' EQUALITY presentationAddressMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.43 SINGLE-VALUE )
olcAttributeTypes: {26}( 2.5.4.30 NAME 'supportedApplicationContext' DESC 'R
FC2256: supported application context' EQUALITY objectIdentifierMatch SYNTA
X 1.3.6.1.4.1.1466.115.121.1.38 )
olcAttributeTypes: {27}( 2.5.4.31 NAME 'member' DESC 'RFC2256: member of a g
roup' SUP distinguishedName )
olcAttributeTypes: {28}( 2.5.4.32 NAME 'owner' DESC 'RFC2256: owner (of the
object)' SUP distinguishedName )
olcAttributeTypes: {29}( 2.5.4.33 NAME 'roleOccupant' DESC 'RFC2256: occupan
t of role' SUP distinguishedName )
olcAttributeTypes: {30}( 2.5.4.36 NAME 'userCertificate' DESC 'RFC2256: X.50
9 user certificate, use ;binary' EQUALITY certificateExactMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.8 )
olcAttributeTypes: {31}( 2.5.4.37 NAME 'cACertificate' DESC 'RFC2256: X.509
CA certificate, use ;binary' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.8 )
olcAttributeTypes: {32}( 2.5.4.38 NAME 'authorityRevocationList' DESC 'RFC22
56: X.509 authority revocation list, use ;binary' SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.9 )
olcAttributeTypes: {33}( 2.5.4.39 NAME 'certificateRevocationList' DESC 'RFC
2256: X.509 certificate revocation list, use ;binary' SYNTAX 1.3.6.1.4.1.14
66.115.121.1.9 )
olcAttributeTypes: {34}( 2.5.4.40 NAME 'crossCertificatePair' DESC 'RFC2256:
X.509 cross certificate pair, use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121
.1.10 )
olcAttributeTypes: {35}( 2.5.4.42 NAME ( 'givenName' 'gn' ) DESC 'RFC2256: f
irst name(s) for which the entity is known by' SUP name )
olcAttributeTypes: {36}( 2.5.4.43 NAME 'initials' DESC 'RFC2256: initials of
some or all of names, but not the surname(s).' SUP name )
olcAttributeTypes: {37}( 2.5.4.44 NAME 'generationQualifier' DESC 'RFC2256:
name qualifier indicating a generation' SUP name )
olcAttributeTypes: {38}( 2.5.4.45 NAME 'x500UniqueIdentifier' DESC 'RFC2256:
X.500 unique identifier' EQUALITY bitStringMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.6 )
olcAttributeTypes: {39}( 2.5.4.46 NAME 'dnQualifier' DESC 'RFC2256: DN quali
fier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {40}( 2.5.4.47 NAME 'enhancedSearchGuide' DESC 'RFC2256:
enhanced search guide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 )
olcAttributeTypes: {41}( 2.5.4.48 NAME 'protocolInformation' DESC 'RFC2256:
protocol information' EQUALITY protocolInformationMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.42 )
olcAttributeTypes: {42}( 2.5.4.50 NAME 'uniqueMember' DESC 'RFC2256: unique
member of a group' EQUALITY uniqueMemberMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.34 )
olcAttributeTypes: {43}( 2.5.4.51 NAME 'houseIdentifier' DESC 'RFC2256: hous
e identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )
olcAttributeTypes: {44}( 2.5.4.52 NAME 'supportedAlgorithms' DESC 'RFC2256:
supported algorithms' SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 )
olcAttributeTypes: {45}( 2.5.4.53 NAME 'deltaRevocationList' DESC 'RFC2256:
delta revocation list; use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )
olcAttributeTypes: {46}( 2.5.4.54 NAME 'dmdName' DESC 'RFC2256: name of DMD'
SUP name )
olcAttributeTypes: {47}( 2.5.4.65 NAME 'pseudonym' DESC 'X.520(4th): pseudon
ym for the object' SUP name )
olcAttributeTypes: {48}( 0.9.2342.19200300.100.1.3 NAME ( 'mail' 'rfc822Mail
box' ) DESC 'RFC1274: RFC822 Mailbox' EQUALITY caseIgnoreIA5Match SUBST
R caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
)
olcAttributeTypes: {49}( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domainCompo
nent' ) DESC 'RFC1274/2247: domain component' EQUALITY caseIgnoreIA5Match S
UBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SIN
GLE-VALUE )
olcAttributeTypes: {50}( 0.9.2342.19200300.100.1.37 NAME 'associatedDomain'
DESC 'RFC1274: domain associated with object' EQUALITY caseIgnoreIA5Match S
UBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {51}( 1.2.840.113549.1.9.1 NAME ( 'email' 'emailAddress'
'pkcs9email' ) DESC 'RFC3280: legacy attribute for email addresses in DNs'
EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.26{128} )
olcObjectClasses: {0}( 2.5.6.2 NAME 'country' DESC 'RFC2256: a country' SUP
top STRUCTURAL MUST c MAY ( searchGuide $ description ) )
olcObjectClasses: {1}( 2.5.6.3 NAME 'locality' DESC 'RFC2256: a locality' SU
P top STRUCTURAL MAY ( street $ seeAlso $ searchGuide $ st $ l $ descriptio
n ) )
olcObjectClasses: {2}( 2.5.6.4 NAME 'organization' DESC 'RFC2256: an organiz
ation' SUP top STRUCTURAL MUST o MAY ( userPassword $ searchGuide $ seeAlso
$ businessCategory $ x121Address $ registeredAddress $ destinationIndicato
r $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ tel
ephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street
$ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName
$ st $ l $ description ) )
olcObjectClasses: {3}( 2.5.6.5 NAME 'organizationalUnit' DESC 'RFC2256: an o
rganizational unit' SUP top STRUCTURAL MUST ou MAY ( userPassword $ searchG
uide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ desti
nationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalId
entifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNu
mber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDelive
ryOfficeName $ st $ l $ description ) )
olcObjectClasses: {4}( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP to
p STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAls
o $ description ) )
olcObjectClasses: {5}( 2.5.6.7 NAME 'organizationalPerson' DESC 'RFC2256: an
organizational person' SUP person STRUCTURAL MAY ( title $ x121Address $ r
egisteredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNu
mber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumbe
r $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ posta
lAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )
olcObjectClasses: {6}( 2.5.6.8 NAME 'organizationalRole' DESC 'RFC2256: an o
rganizational role' SUP top STRUCTURAL MUST cn MAY ( x121Address $ register
edAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $
teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ fac
simileTelephoneNumber $ seeAlso $ roleOccupant $ preferredDeliveryMethod $
street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOffic
eName $ ou $ st $ l $ description ) )
olcObjectClasses: {7}( 2.5.6.9 NAME 'groupOfNames' DESC 'RFC2256: a group of
names (DNs)' SUP top STRUCTURAL MUST ( member $ cn ) MAY ( businessCategor
y $ seeAlso $ owner $ ou $ o $ description ) )
olcObjectClasses: {8}( 2.5.6.10 NAME 'residentialPerson' DESC 'RFC2256: an r
esidential person' SUP person STRUCTURAL MUST l MAY ( businessCategory $ x1
21Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMet
hod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internati
onaliSDNNumber $ facsimileTelephoneNumber $ preferredDeliveryMethod $ stree
t $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName
$ st $ l ) )
olcObjectClasses: {9}( 2.5.6.11 NAME 'applicationProcess' DESC 'RFC2256: an
application process' SUP top STRUCTURAL MUST cn MAY ( seeAlso $ ou $ l $ de
scription ) )
olcObjectClasses: {10}( 2.5.6.12 NAME 'applicationEntity' DESC 'RFC2256: an
application entity' SUP top STRUCTURAL MUST ( presentationAddress $ cn ) MA
Y ( supportedApplicationContext $ seeAlso $ ou $ o $ l $ description ) )
olcObjectClasses: {11}( 2.5.6.13 NAME 'dSA' DESC 'RFC2256: a directory syste
m agent (a server)' SUP applicationEntity STRUCTURAL MAY knowledgeInformati
on )
olcObjectClasses: {12}( 2.5.6.14 NAME 'device' DESC 'RFC2256: a device' SUP
top STRUCTURAL MUST cn MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $
description ) )
olcObjectClasses: {13}( 2.5.6.15 NAME 'strongAuthenticationUser' DESC 'RFC22
56: a strong authentication user' SUP top AUXILIARY MUST userCertificate )
olcObjectClasses: {14}( 2.5.6.16 NAME 'certificationAuthority' DESC 'RFC2256
: a certificate authority' SUP top AUXILIARY MUST ( authorityRevocationList
$ certificateRevocationList $ cACertificate ) MAY crossCertificatePair )
olcObjectClasses: {15}( 2.5.6.17 NAME 'groupOfUniqueNames' DESC 'RFC2256: a
group of unique names (DN and Unique Identifier)' SUP top STRUCTURAL MUST (
uniqueMember $ cn ) MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ de
scription ) )
olcObjectClasses: {16}( 2.5.6.18 NAME 'userSecurityInformation' DESC 'RFC225
6: a user security information' SUP top AUXILIARY MAY ( supportedAlgorithms
) )
olcObjectClasses: {17}( 2.5.6.16.2 NAME 'certificationAuthority-V2' SUP cert
ificationAuthority AUXILIARY MAY ( deltaRevocationList ) )
olcObjectClasses: {18}( 2.5.6.19 NAME 'cRLDistributionPoint' SUP top STRUCTU
RAL MUST ( cn ) MAY ( certificateRevocationList $ authorityRevocationList $
deltaRevocationList ) )
olcObjectClasses: {19}( 2.5.6.20 NAME 'dmd' SUP top STRUCTURAL MUST ( dmdNam
e ) MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Add
ress $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $
telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationali
SDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode
$ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )
olcObjectClasses: {20}( 2.5.6.21 NAME 'pkiUser' DESC 'RFC2587: a PKI user' S
UP top AUXILIARY MAY userCertificate )
olcObjectClasses: {21}( 2.5.6.22 NAME 'pkiCA' DESC 'RFC2587: PKI certificate
authority' SUP top AUXILIARY MAY ( authorityRevocationList $ certificateRe
vocationList $ cACertificate $ crossCertificatePair ) )
olcObjectClasses: {22}( 2.5.6.23 NAME 'deltaCRL' DESC 'RFC2587: PKI user' SU
P top AUXILIARY MAY deltaRevocationList )
olcObjectClasses: {23}( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject' DESC 'R
FC2079: object that contains the URI attribute type' MAY ( labeledURI ) SUP
top AUXILIARY )
olcObjectClasses: {24}( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObjec
t' DESC 'RFC1274: simple security object' SUP top AUXILIARY MUST userPasswo
rd )
olcObjectClasses: {25}( 1.3.6.1.4.1.1466.344 NAME 'dcObject' DESC 'RFC2247:
domain component object' SUP top AUXILIARY MUST dc )
olcObjectClasses: {26}( 1.3.6.1.1.3.1 NAME 'uidObject' DESC 'RFC2377: uid ob
ject' SUP top AUXILIARY MUST uid )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c26a290-7ddc-103b-9ebf-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.324162Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,178 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 3133677b
dn: cn={1}cosine
objectClass: olcSchemaConfig
cn: {1}cosine
olcAttributeTypes: {0}( 0.9.2342.19200300.100.1.2 NAME 'textEncodedORAddress
' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {1}( 0.9.2342.19200300.100.1.4 NAME 'info' DESC 'RFC1274:
general information' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} )
olcAttributeTypes: {2}( 0.9.2342.19200300.100.1.5 NAME ( 'drink' 'favouriteD
rink' ) DESC 'RFC1274: favorite drink' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {3}( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' DESC 'RF
C1274: room number' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {4}( 0.9.2342.19200300.100.1.7 NAME 'photo' DESC 'RFC1274
: photo (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23{25000} )
olcAttributeTypes: {5}( 0.9.2342.19200300.100.1.8 NAME 'userClass' DESC 'RFC
1274: category of user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {6}( 0.9.2342.19200300.100.1.9 NAME 'host' DESC 'RFC1274:
host computer' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S
YNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {7}( 0.9.2342.19200300.100.1.10 NAME 'manager' DESC 'RFC1
274: DN of manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466
.115.121.1.12 )
olcAttributeTypes: {8}( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier'
DESC 'RFC1274: unique identifier of document' EQUALITY caseIgnoreMatch SUB
STR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {9}( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' DESC
'RFC1274: title of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSub
stringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {10}( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' D
ESC 'RFC1274: version of document' EQUALITY caseIgnoreMatch SUBSTR caseIgno
reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {11}( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' DE
SC 'RFC1274: DN of author of document' EQUALITY distinguishedNameMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {12}( 0.9.2342.19200300.100.1.15 NAME 'documentLocation'
DESC 'RFC1274: location of document original' EQUALITY caseIgnoreMatch SUBS
TR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {13}( 0.9.2342.19200300.100.1.20 NAME ( 'homePhone' 'home
TelephoneNumber' ) DESC 'RFC1274: home telephone number' EQUALITY telephone
NumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.50 )
olcAttributeTypes: {14}( 0.9.2342.19200300.100.1.21 NAME 'secretary' DESC 'R
FC1274: DN of secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1
.1466.115.121.1.12 )
olcAttributeTypes: {15}( 0.9.2342.19200300.100.1.22 NAME 'otherMailbox' SYNT
AX 1.3.6.1.4.1.1466.115.121.1.39 )
olcAttributeTypes: {16}( 0.9.2342.19200300.100.1.26 NAME 'aRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {17}( 0.9.2342.19200300.100.1.27 NAME 'mDRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {18}( 0.9.2342.19200300.100.1.28 NAME 'mXRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {19}( 0.9.2342.19200300.100.1.29 NAME 'nSRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {20}( 0.9.2342.19200300.100.1.30 NAME 'sOARecord' EQUALIT
Y caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {21}( 0.9.2342.19200300.100.1.31 NAME 'cNAMERecord' EQUAL
ITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {22}( 0.9.2342.19200300.100.1.38 NAME 'associatedName' DE
SC 'RFC1274: DN of entry associated with domain' EQUALITY distinguishedName
Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {23}( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress'
DESC 'RFC1274: home postal address' EQUALITY caseIgnoreListMatch SUBSTR ca
seIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
olcAttributeTypes: {24}( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' DES
C 'RFC1274: personal title' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubst
ringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {25}( 0.9.2342.19200300.100.1.41 NAME ( 'mobile' 'mobileT
elephoneNumber' ) DESC 'RFC1274: mobile telephone number' EQUALITY telephon
eNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.50 )
olcAttributeTypes: {26}( 0.9.2342.19200300.100.1.42 NAME ( 'pager' 'pagerTel
ephoneNumber' ) DESC 'RFC1274: pager telephone number' EQUALITY telephoneNu
mberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115
.121.1.50 )
olcAttributeTypes: {27}( 0.9.2342.19200300.100.1.43 NAME ( 'co' 'friendlyCou
ntryName' ) DESC 'RFC1274: friendly country name' EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {28}( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier'
DESC 'RFC1274: unique identifer' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.15{256} )
olcAttributeTypes: {29}( 0.9.2342.19200300.100.1.45 NAME 'organizationalStat
us' DESC 'RFC1274: organizational status' EQUALITY caseIgnoreMatch SUBSTR c
aseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {30}( 0.9.2342.19200300.100.1.46 NAME 'janetMailbox' DESC
'RFC1274: Janet mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5S
ubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
olcAttributeTypes: {31}( 0.9.2342.19200300.100.1.47 NAME 'mailPreferenceOpti
on' DESC 'RFC1274: mail preference option' SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 )
olcAttributeTypes: {32}( 0.9.2342.19200300.100.1.48 NAME 'buildingName' DESC
'RFC1274: name of building' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubs
tringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {33}( 0.9.2342.19200300.100.1.49 NAME 'dSAQuality' DESC '
RFC1274: DSA Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.19 SINGLE-VALUE )
olcAttributeTypes: {34}( 0.9.2342.19200300.100.1.50 NAME 'singleLevelQuality
' DESC 'RFC1274: Single Level Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13
SINGLE-VALUE )
olcAttributeTypes: {35}( 0.9.2342.19200300.100.1.51 NAME 'subtreeMinimumQual
ity' DESC 'RFC1274: Subtree Mininum Quality' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.13 SINGLE-VALUE )
olcAttributeTypes: {36}( 0.9.2342.19200300.100.1.52 NAME 'subtreeMaximumQual
ity' DESC 'RFC1274: Subtree Maximun Quality' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.13 SINGLE-VALUE )
olcAttributeTypes: {37}( 0.9.2342.19200300.100.1.53 NAME 'personalSignature'
DESC 'RFC1274: Personal Signature (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.23 )
olcAttributeTypes: {38}( 0.9.2342.19200300.100.1.54 NAME 'dITRedirect' DESC
'RFC1274: DIT Redirect' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.12 )
olcAttributeTypes: {39}( 0.9.2342.19200300.100.1.55 NAME 'audio' DESC 'RFC12
74: audio (u-law)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.4{25000} )
olcAttributeTypes: {40}( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher'
DESC 'RFC1274: publisher of document' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcObjectClasses: {0}( 0.9.2342.19200300.100.4.4 NAME ( 'pilotPerson' 'newPi
lotPerson' ) SUP person STRUCTURAL MAY ( userid $ textEncodedORAddress $ rf
c822Mailbox $ favouriteDrink $ roomNumber $ userClass $ homeTelephoneNumber
$ homePostalAddress $ secretary $ personalTitle $ preferredDeliveryMethod
$ businessCategory $ janetMailbox $ otherMailbox $ mobileTelephoneNumber $
pagerTelephoneNumber $ organizationalStatus $ mailPreferenceOption $ person
alSignature ) )
olcObjectClasses: {1}( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRU
CTURAL MUST userid MAY ( description $ seeAlso $ localityName $ organizatio
nName $ organizationalUnitName $ host ) )
olcObjectClasses: {2}( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STR
UCTURAL MUST documentIdentifier MAY ( commonName $ description $ seeAlso $
localityName $ organizationName $ organizationalUnitName $ documentTitle $
documentVersion $ documentAuthor $ documentLocation $ documentPublisher ) )
olcObjectClasses: {3}( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTU
RAL MUST commonName MAY ( roomNumber $ description $ seeAlso $ telephoneNum
ber ) )
olcObjectClasses: {4}( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP t
op STRUCTURAL MUST commonName MAY ( description $ seeAlso $ telephonenumber
$ localityName $ organizationName $ organizationalUnitName ) )
olcObjectClasses: {5}( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRU
CTURAL MUST domainComponent MAY ( associatedName $ organizationName $ descr
iption $ businessCategory $ seeAlso $ searchGuide $ userPassword $ locality
Name $ stateOrProvinceName $ streetAddress $ physicalDeliveryOfficeName $ p
ostalAddress $ postalCode $ postOfficeBox $ streetAddress $ facsimileTeleph
oneNumber $ internationalISDNNumber $ telephoneNumber $ teletexTerminalIden
tifier $ telexNumber $ preferredDeliveryMethod $ destinationIndicator $ reg
isteredAddress $ x121Address ) )
olcObjectClasses: {6}( 0.9.2342.19200300.100.4.14 NAME 'RFC822localPart' SUP
domain STRUCTURAL MAY ( commonName $ surname $ description $ seeAlso $ tel
ephoneNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ po
stOfficeBox $ streetAddress $ facsimileTelephoneNumber $ internationalISDNN
umber $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ preferr
edDeliveryMethod $ destinationIndicator $ registeredAddress $ x121Address )
)
olcObjectClasses: {7}( 0.9.2342.19200300.100.4.15 NAME 'dNSDomain' SUP domai
n STRUCTURAL MAY ( ARecord $ MDRecord $ MXRecord $ NSRecord $ SOARecord $ C
NAMERecord ) )
olcObjectClasses: {8}( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject'
DESC 'RFC1274: an object related to an domain' SUP top AUXILIARY MUST asso
ciatedDomain )
olcObjectClasses: {9}( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP
country STRUCTURAL MUST friendlyCountryName )
olcObjectClasses: {10}( 0.9.2342.19200300.100.4.20 NAME 'pilotOrganization'
SUP ( organization $ organizationalUnit ) STRUCTURAL MAY buildingName )
olcObjectClasses: {11}( 0.9.2342.19200300.100.4.21 NAME 'pilotDSA' SUP dsa S
TRUCTURAL MAY dSAQuality )
olcObjectClasses: {12}( 0.9.2342.19200300.100.4.22 NAME 'qualityLabelledData
' SUP top AUXILIARY MUST dsaQuality MAY ( subtreeMinimumQuality $ subtreeMa
ximumQuality ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c286ac6-7ddc-103b-9ec0-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.335841Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,108 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 8c7d43da
dn: cn={2}nis
objectClass: olcSchemaConfig
cn: {2}nis
olcAttributeTypes: {0}( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field;
the common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Substrings
Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absol
ute path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4
.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to
the login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121
.1.26 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY int
egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExac
tIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.26 )
olcAttributeTypes: {11}( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY
caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.26 )
olcAttributeTypes: {12}( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Net
group triple' SYNTAX 1.3.6.1.1.1.0.0 )
olcAttributeTypes: {13}( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' EQUALITY inte
gerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' SUP name
)
olcAttributeTypes: {15}( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {17}( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IP addre
ss' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
olcAttributeTypes: {18}( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP ne
twork' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128
} SINGLE-VALUE )
olcAttributeTypes: {19}( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP ne
tmask' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128
} SINGLE-VALUE )
olcAttributeTypes: {20}( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC addres
s' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
olcAttributeTypes: {21}( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.boo
tparamd parameter' SYNTAX 1.3.6.1.1.1.0.1 )
olcAttributeTypes: {22}( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image n
ame' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {23}( 1.3.6.1.1.1.1.26 NAME 'nisMapName' SUP name )
olcAttributeTypes: {24}( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' EQUALITY caseEx
actIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.26{1024} SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction
of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ u
idNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ ge
cos $ description ) )
olcObjectClasses: {1}( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional
attributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPass
word $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowIna
ctive $ shadowExpire $ shadowFlag $ description ) )
olcObjectClasses: {2}( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction o
f a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( use
rPassword $ memberUid $ description ) )
olcObjectClasses: {3}( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an
Internet Protocol service' SUP top STRUCTURAL MUST ( cn $ ipServicePort $
ipServiceProtocol ) MAY description )
olcObjectClasses: {4}( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction o
f an IP protocol' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber $ descrip
tion ) MAY description )
olcObjectClasses: {5}( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an
ONC/RPC binding' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber $ description
) MAY description )
olcObjectClasses: {6}( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a
host, an IP device' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( l $
description $ manager ) )
olcObjectClasses: {7}( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of
an IP network' SUP top STRUCTURAL MUST ( cn $ ipNetworkNumber ) MAY ( ipNe
tmaskNumber $ l $ description $ manager ) )
olcObjectClasses: {8}( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction
of a netgroup' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberN
isNetgroup $ description ) )
olcObjectClasses: {9}( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstrac
tion of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description )
olcObjectClasses: {10}( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in
a NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) MAY de
scription )
olcObjectClasses: {11}( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device
with a MAC address' SUP top AUXILIARY MAY macAddress )
olcObjectClasses: {12}( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A devic
e with boot parameters' SUP top AUXILIARY MAY ( bootFile $ bootParameter )
)
structuralObjectClass: olcSchemaConfig
entryUUID: 5c2966ce-7ddc-103b-9ec1-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.342294Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,49 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 b409a0c2
dn: cn={3}inetorgperson
objectClass: olcSchemaConfig
cn: {3}inetorgperson
olcAttributeTypes: {0}( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'RFC2
798: vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR
caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {1}( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC
'RFC2798: identifies a department within an organization' EQUALITY caseIgn
oreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.15 )
olcAttributeTypes: {2}( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'R
FC2798: preferred name to be used when displaying entries' EQUALITY caseIgn
oreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.15 SINGLE-VALUE )
olcAttributeTypes: {3}( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC '
RFC2798: numerically identifies an employee within an organization' EQUALIT
Y caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {4}( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'RF
C2798: type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR cas
eIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {5}( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'RF
C2798: a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )
olcAttributeTypes: {6}( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DE
SC 'RFC2798: preferred written or spoken language for a person' EQUALITY ca
seIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15 SINGLE-VALUE )
olcAttributeTypes: {7}( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate'
DESC 'RFC2798: PKCS#7 SignedData used to support S/MIME' SYNTAX 1.3.6.1.4.
1.1466.115.121.1.5 )
olcAttributeTypes: {8}( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'RF
C2798: personal identity information, a PKCS #12 PFX' SYNTAX 1.3.6.1.4.1.14
66.115.121.1.5 )
olcObjectClasses: {0}( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' DESC 'RF
C2798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL
MAY ( audio $ businessCategory $ carLicense $ departmentNumber $ displayNam
e $ employeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddre
ss $ initials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ page
r $ photo $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIden
tifier $ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c2a5016-7ddc-103b-9ec2-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.348266Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,65 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 cded21ef
dn: cn={4}ppolicy
objectClass: olcSchemaConfig
cn: {4}ppolicy
olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.8.1.1 NAME 'pwdAttribute' EQUALI
TY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.8.1.2 NAME 'pwdMinAge' EQUALITY
integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.42.2.27.8.1.3 NAME 'pwdMaxAge' EQUALITY
integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.8.1.4 NAME 'pwdInHistory' EQUALI
TY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.42.2.27.8.1.5 NAME 'pwdCheckQuality' EQU
ALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.42.2.27.8.1.6 NAME 'pwdMinLength' EQUALI
TY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.42.2.27.8.1.7 NAME 'pwdExpireWarning' EQ
UALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.42.2.27.8.1.8 NAME 'pwdGraceAuthNLimit'
EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.42.2.27.8.1.9 NAME 'pwdLockout' EQUALITY
booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.42.2.27.8.1.10 NAME 'pwdLockoutDuration'
EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.14
66.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.42.2.27.8.1.11 NAME 'pwdMaxFailure' EQU
ALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.42.2.27.8.1.12 NAME 'pwdFailureCountInt
erval' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {12}( 1.3.6.1.4.1.42.2.27.8.1.13 NAME 'pwdMustChange' EQU
ALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.42.2.27.8.1.14 NAME 'pwdAllowUserChange
' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.42.2.27.8.1.15 NAME 'pwdSafeModify' EQU
ALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.4754.1.99.1 NAME 'pwdCheckModule' DESC
'Loadable module that instantiates "check_password() function' EQUALITY cas
eExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.42.2.27.8.1.30 NAME 'pwdMaxRecordedFail
ure' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.4754.2.99.1 NAME 'pwdPolicyChecker' SUP t
op AUXILIARY MAY pwdCheckModule )
olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.8.2.1 NAME 'pwdPolicy' SUP top AU
XILIARY MUST pwdAttribute MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdC
heckQuality $ pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLoc
kout $ pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ pwdMu
stChange $ pwdAllowUserChange $ pwdSafeModify $ pwdMaxRecordedFailure ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c51364a-7ddc-103b-8d41-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.603187Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,175 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 7d21399c
dn: cn={5}kopano
objectClass: olcSchemaConfig
cn: {5}kopano
olcAttributeTypes: {0}( 1.3.6.1.4.1.47732.1.1.1.1 NAME 'kopanoQuotaOverride'
DESC 'KOPANO: Override child quota' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.47732.1.1.1.2 NAME 'kopanoQuotaWarn' DES
C 'KOPANO: Warning quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.47732.1.1.1.3 NAME 'kopanoQuotaSoft' DES
C 'KOPANO: Soft quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.47732.1.1.1.4 NAME 'kopanoQuotaHard' DES
C 'KOPANO: Hard quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.47732.1.1.1.5 NAME 'kopanoUserDefaultQuo
taOverride' DESC 'KOPANO: Override User default quota for children' EQUALIT
Y integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.47732.1.1.1.6 NAME 'kopanoUserDefaultQuo
taWarn' DESC 'KOPANO: User default warning quota size in MB' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.47732.1.1.1.7 NAME 'kopanoUserDefaultQuo
taSoft' DESC 'KOPANO: User default soft quota size in MB' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.47732.1.1.1.8 NAME 'kopanoUserDefaultQuo
taHard' DESC 'KOPANO: User default hard quota size in MB' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.47732.1.1.2.1 NAME 'kopanoAdmin' DESC 'K
OPANO: Administrator of kopano' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.14
66.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.47732.1.1.2.2 NAME 'kopanoSharedStoreOnl
y' DESC 'KOPANO: is store a shared store' EQUALITY integerMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.47732.1.1.2.3 NAME 'kopanoAccount' DESC
'KOPANO: entry is a part of kopano' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.47732.1.1.2.4 NAME 'kopanoSendAsPrivile
ge' DESC 'KOPANO: Users may directly send email as this user' EQUALITY case
IgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.15 )
olcAttributeTypes: {12}( 1.3.6.1.4.1.47732.1.1.2.5 NAME 'kopanoMrAccept' DES
C 'KOPANO: user should auto-accept meeting requests' EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.47732.1.1.2.6 NAME 'kopanoMrDeclineConf
lict' DESC 'KOPANO: user should automatically decline conflicting meeting r
equests' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-
VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.47732.1.1.2.7 NAME 'kopanoMrDeclineRecu
rring' DESC 'KOPANO: user should automatically decline recurring meeting re
quests' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-V
ALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.47732.1.1.2.8 NAME 'kopanoId' DESC 'KOP
ANO: Generic unique ID' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.40 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.47732.1.1.2.9 NAME 'kopanoResourceType'
DESC 'KOPANO: for shared stores, resource is type Room or Equipment' EQUAL
ITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {17}( 1.3.6.1.4.1.47732.1.1.2.10 NAME 'kopanoResourceCapa
city' DESC 'KOPANO: number of rooms or equipment available' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {18}( 1.3.6.1.4.1.47732.1.1.2.11 NAME 'kopanoHidden' DESC
'KOPANO: This object should be hidden from address book' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {19}( 1.3.6.1.4.1.47732.1.1.2.13 NAME 'kopanoEnabledFeatu
res' DESC 'KOPANO: This user has these features explicitly enabled' EQUALIT
Y caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {20}( 1.3.6.1.4.1.47732.1.1.2.14 NAME 'kopanoDisabledFeat
ures' DESC 'KOPANO: This user has these features explicitly disabled' EQUAL
ITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {21}( 1.3.6.1.4.1.47732.1.1.3.1 NAME 'kopanoAliases' DESC
'KOPANO: All other email addresses for this user' EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {22}( 1.3.6.1.4.1.47732.1.1.4.1 NAME 'kopanoUserServer' D
ESC 'KOPANO: Home server for the user' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {23}( 1.3.6.1.4.1.47732.1.1.6.1 NAME 'kopanoUserArchiveSe
rvers' DESC 'KOPANO: List of server names that contain an archive store for
the user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {24}( 1.3.6.1.4.1.47732.1.1.6.2 NAME 'kopanoUserArchiveCo
uplings' DESC 'KOPANO: List of username:foldername pairs that specify many-
to-one archive locations' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstri
ngsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {25}( 1.3.6.1.4.1.47732.1.2.2.1 NAME 'kopanoSecurityGroup
' DESC 'KOPANO: group has security possibilities' EQUALITY integerMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {26}( 1.3.6.1.4.1.47732.1.3.2.4 NAME 'kopanoViewPrivilege
' DESC 'KOPANO: Companies with view privileges over selected company' EQUAL
ITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.15 )
olcAttributeTypes: {27}( 1.3.6.1.4.1.47732.1.3.2.5 NAME 'kopanoAdminPrivileg
e' DESC 'KOPANO: Users from different companies which are administrator ove
r selected company' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {28}( 1.3.6.1.4.1.47732.1.3.2.6 NAME 'kopanoSystemAdmin'
DESC 'KOPANO: The user who is the system administrator for this company' EQ
UALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {29}( 1.3.6.1.4.1.47732.1.3.1.5 NAME 'kopanoQuotaUserWarn
ingRecipients' DESC 'KOPANO: Users who will receive a notification email wh
en a user exceeds his quota' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubs
tringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {30}( 1.3.6.1.4.1.47732.1.3.1.6 NAME 'kopanoQuotaCompanyW
arningRecipients' DESC 'KOPANO: Users who will receive a notification email
when a company exceeds its quota' EQUALITY caseIgnoreMatch SUBSTR caseIgno
reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {31}( 1.3.6.1.4.1.47732.1.3.4.1 NAME 'kopanoCompanyServer
' DESC 'KOPANO: Home server for the public folders for a company' EQUALITY
caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {32}( 1.3.6.1.4.1.47732.1.4.4.1 NAME 'kopanoHttpPort' DES
C 'KOPANO: Port for the http connection' EQUALITY integerMatch SYNTAX 1.3.6
.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {33}( 1.3.6.1.4.1.47732.1.4.4.2 NAME 'kopanoSslPort' DESC
'KOPANO: Port for the ssl connection' EQUALITY integerMatch SYNTAX 1.3.6.1
.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {34}( 1.3.6.1.4.1.47732.1.4.4.3 NAME 'kopanoFilePath' DES
C 'KOPANO: The Unix socket or named pipe to the server' EQUALITY caseIgnore
Match SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcAttributeTypes: {35}( 1.3.6.1.4.1.47732.1.4.4.4 NAME 'kopanoContainsPubli
c' DESC 'KOPANO: This server contains the public store' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {36}( 1.3.6.1.4.1.47732.1.4.4.6 NAME 'kopanoProxyURL' DES
C 'KOPANO: Full proxy URL for this server' EQUALITY caseIgnoreMatch SUBSTR
caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
)
olcAttributeTypes: {37}( 1.3.6.1.4.1.47732.1.5.5.1 NAME 'kopanoFilter' DESC
'KOPANO: LDAP Filter to apply' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSu
bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {38}( 1.3.6.1.4.1.47732.1.5.5.2 NAME 'kopanoBase' DESC 'K
OPANO: LDAP Search base to apply' EQUALITY caseIgnoreMatch SUBSTR caseIgnor
eSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.47732.1.1.0.0 NAME 'kopano-user' DESC 'KO
PANO: an user of Kopano' SUP top AUXILIARY MUST cn MAY ( kopanoQuotaOverrid
e $ kopanoQuotaWarn $ kopanoQuotaSoft $ kopanoSendAsPrivilege $ kopanoQuota
Hard $ kopanoAdmin $ kopanoSharedStoreOnly $ kopanoResourceType $ kopanoRes
ourceCapacity $ kopanoAccount $ kopanoHidden $ kopanoAliases $ kopanoUserSe
rver $ kopanoEnabledFeatures $ kopanoDisabledFeatures $ kopanoUserArchiveSe
rvers $ kopanoUserArchiveCouplings $ uidNumber ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.47732.1.6.0.0 NAME 'kopano-contact' DESC
'KOPANO: a contact of Kopano' SUP top AUXILIARY MUST ( cn $ uidNumber ) MAY
( kopanoSendAsPrivilege $ kopanoHidden $ kopanoAliases $ kopanoAccount ) )
olcObjectClasses: {2}( 1.3.6.1.4.1.47732.1.2.0.0 NAME 'kopano-group' DESC 'K
OPANO: a group of Kopano' SUP top AUXILIARY MUST cn MAY ( kopanoAccount $ k
opanoHidden $ mail $ kopanoAliases $ kopanoSecurityGroup $ kopanoSendAsPriv
ilege $ gidNumber ) )
olcObjectClasses: {3}( 1.3.6.1.4.1.47732.1.3.0.0 NAME 'kopano-company' DESC
'KOPANO: a company of Kopano' SUP top AUXILIARY MUST ou MAY ( kopanoAccount
$ kopanoHidden $ kopanoViewPrivilege $ kopanoAdminPrivilege $ kopanoSystem
Admin $ kopanoQuotaOverride $ kopanoQuotaWarn $ kopanoUserDefaultQuotaOverr
ide $ kopanoUserDefaultQuotaWarn $ kopanoUserDefaultQuotaSoft $ kopanoUserD
efaultQuotaHard $ kopanoQuotaUserWarningRecipients $ kopanoQuotaCompanyWarn
ingRecipients $ kopanoCompanyServer ) )
olcObjectClasses: {4}( 1.3.6.1.4.1.47732.1.4.0.0 NAME 'kopano-server' DESC '
KOPANO: a Kopano server' SUP top AUXILIARY MUST cn MAY ( kopanoAccount $ ko
panoHidden $ kopanoHttpPort $ kopanoSslPort $ kopanoFilePath $ kopanoContai
nsPublic $ kopanoProxyURL ) )
olcObjectClasses: {5}( 1.3.6.1.4.1.47732.1.5.0.0 NAME 'kopano-addresslist' D
ESC 'KOPANO: a Kopano Addresslist' SUP top STRUCTURAL MUST cn MAY ( kopanoA
ccount $ kopanoHidden $ kopanoFilter $ kopanoBase ) )
olcObjectClasses: {6}( 1.3.6.1.4.1.47732.1.7.0.0 NAME 'kopano-dynamicgroup'
DESC 'KOPANO: a Kopano dynamic group' SUP top STRUCTURAL MUST cn MAY ( kopa
noAccount $ kopanoHidden $ mail $ kopanoAliases $ kopanoFilter $ kopanoBase
) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c5ee420-7ddc-103b-8d42-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.692846Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 815bdd09
dn: cn={6}openssh-lpk
objectClass: olcSchemaConfig
cn: {6}openssh-lpk
olcAttributeTypes: {0}( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' D
ESC 'MANDATORY: OpenSSH Public key' EQUALITY octetStringMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.40 )
olcObjectClasses: {0}( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' DE
SC 'MANDATORY: OpenSSH LPK objectclass' SUP top AUXILIARY MAY ( sshPublicKe
y $ uid ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c6226ee-7ddc-103b-8d43-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.714216Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,50 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 38be59a6
dn: cn={7}postfix-book
objectClass: olcSchemaConfig
cn: {7}postfix-book
olcAttributeTypes: {0}( 1.3.6.1.4.1.29426.1.10.1 NAME 'mailHomeDirectory' DE
SC 'The absolute path to the mail user home directory' EQUALITY caseExactIA
5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.29426.1.10.2 NAME 'mailAlias' DESC 'RFC8
22 Mailbox - mail alias' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Su
bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
olcAttributeTypes: {2}( 1.3.6.1.4.1.29426.1.10.3 NAME 'mailUidNumber' DESC '
UID required to access the mailbox' EQUALITY integerMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.29426.1.10.4 NAME 'mailGidNumber' DESC '
GID required to access the mailbox' EQUALITY integerMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.29426.1.10.5 NAME 'mailEnabled' DESC 'TR
UE to enable, FALSE to disable account' EQUALITY booleanMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.29426.1.10.6 NAME 'mailGroupMember' DESC
'Name of a mail distribution list' EQUALITY caseExactIA5Match SYNTAX 1.3.6
.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {6}( 1.3.6.1.4.1.29426.1.10.7 NAME 'mailQuota' DESC 'Mail
quota limit in kilobytes' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.14
66.115.121.1.26 )
olcAttributeTypes: {7}( 1.3.6.1.4.1.29426.1.10.8 NAME 'mailStorageDirectory'
DESC 'The absolute path to the mail users mailbox' EQUALITY caseExactIA5Ma
tch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.29426.1.10.9 NAME 'mailSieveRuleSource'
DESC 'Sun ONE Messaging Server defined attribute' SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.26 X-ORIGIN 'Sun ONE Messaging Server' )
olcAttributeTypes: {9}( 1.3.6.1.4.1.29426.1.10.10 NAME 'mailForwardingAddres
s' DESC 'Address(es) to forward all incoming messages to.' EQUALITY caseIgn
oreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{320} )
olcObjectClasses: {0}( 1.3.6.1.4.1.29426.1.2.2.1 NAME 'PostfixBookMailAccoun
t' DESC 'Mail account used in Postfix Book' SUP top AUXILIARY MUST mail MAY
( mailHomeDirectory $ mailAlias $ mailGroupMember $ mailUidNumber $ mailGi
dNumber $ mailEnabled $ mailQuota $ mailStorageDirectory $ mailSieveRuleSou
rce ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.29426.1.2.2.2 NAME 'PostfixBookMailForwar
d' DESC 'Mail forward used in Postfix Book' SUP top AUXILIARY MUST ( mail $
mailAlias ) MAY mailForwardingAddress )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c658af0-7ddc-103b-8d44-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.736438Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,235 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 41b81a66
dn: cn={8}samba
objectClass: olcSchemaConfig
cn: {8}samba
olcAttributeTypes: {0}( 1.3.6.1.4.1.7165.2.1.24 NAME 'sambaLMPassword' DESC
'LanManager Password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.26{32} SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.7165.2.1.25 NAME 'sambaNTPassword' DESC
'MD4 hash of the unicode password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6
.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags' DESC '
Account Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.
1.26{16} SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet' DESC
'Timestamp of the last password update' EQUALITY integerMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.7165.2.1.28 NAME 'sambaPwdCanChange' DES
C 'Timestamp of when the user is allowed to update the password' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.7165.2.1.29 NAME 'sambaPwdMustChange' DE
SC 'Timestamp of when the password will expire' EQUALITY integerMatch SYNTA
X 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.7165.2.1.30 NAME 'sambaLogonTime' DESC '
Timestamp of last logon' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.7165.2.1.31 NAME 'sambaLogoffTime' DESC
'Timestamp of last logoff' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.7165.2.1.32 NAME 'sambaKickoffTime' DESC
'Timestamp of when the user will be logged off automatically' EQUALITY int
egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.7165.2.1.48 NAME 'sambaBadPasswordCount'
DESC 'Bad password attempt count' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1
.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.7165.2.1.49 NAME 'sambaBadPasswordTime'
DESC 'Time of the last bad password attempt' EQUALITY integerMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.7165.2.1.55 NAME 'sambaLogonHours' DESC
'Logon Hours' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.
1.26{42} SINGLE-VALUE )
olcAttributeTypes: {12}( 1.3.6.1.4.1.7165.2.1.33 NAME 'sambaHomeDrive' DESC
'Driver letter of home directory mapping' EQUALITY caseIgnoreIA5Match SYNTA
X 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.7165.2.1.34 NAME 'sambaLogonScript' DES
C 'Logon script path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.7165.2.1.35 NAME 'sambaProfilePath' DES
C 'Roaming profile path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.7165.2.1.36 NAME 'sambaUserWorkstations
' DESC 'List of user workstations the user is allowed to logon to' EQUALITY
caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.7165.2.1.37 NAME 'sambaHomePath' DESC '
Home directory UNC path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.15{128} )
olcAttributeTypes: {17}( 1.3.6.1.4.1.7165.2.1.38 NAME 'sambaDomainName' DESC
'Windows NT domain to which the user belongs' EQUALITY caseIgnoreMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {18}( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial' DESC
'Base64 encoded user parameter string' EQUALITY caseExactMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {19}( 1.3.6.1.4.1.7165.2.1.54 NAME 'sambaPasswordHistory'
DESC 'Concatenated MD5 hashes of the salted NT passwords used on this acco
unt' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
olcAttributeTypes: {20}( 1.3.6.1.4.1.7165.2.1.20 NAME 'sambaSID' DESC 'Secur
ity ID' EQUALITY caseIgnoreIA5Match SUBSTR caseExactIA5SubstringsMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
olcAttributeTypes: {21}( 1.3.6.1.4.1.7165.2.1.23 NAME 'sambaPrimaryGroupSID'
DESC 'Primary Group Security ID' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.
1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
olcAttributeTypes: {22}( 1.3.6.1.4.1.7165.2.1.51 NAME 'sambaSIDList' DESC 'S
ecurity ID List' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.26{64} )
olcAttributeTypes: {23}( 1.3.6.1.4.1.7165.2.1.19 NAME 'sambaGroupType' DESC
'NT Group Type' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE )
olcAttributeTypes: {24}( 1.3.6.1.4.1.7165.2.1.21 NAME 'sambaNextUserRid' DES
C 'Next NT rid to give our for users' EQUALITY integerMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {25}( 1.3.6.1.4.1.7165.2.1.22 NAME 'sambaNextGroupRid' DE
SC 'Next NT rid to give out for groups' EQUALITY integerMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {26}( 1.3.6.1.4.1.7165.2.1.39 NAME 'sambaNextRid' DESC 'N
ext NT rid to give out for anything' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {27}( 1.3.6.1.4.1.7165.2.1.40 NAME 'sambaAlgorithmicRidBa
se' DESC 'Base at which the samba RID generation algorithm should operate'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {28}( 1.3.6.1.4.1.7165.2.1.41 NAME 'sambaShareName' DESC
'Share Name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcAttributeTypes: {29}( 1.3.6.1.4.1.7165.2.1.42 NAME 'sambaOptionName' DESC
'Option Name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SY
NTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {30}( 1.3.6.1.4.1.7165.2.1.43 NAME 'sambaBoolOption' DESC
'A boolean option' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.7 SINGLE-VALUE )
olcAttributeTypes: {31}( 1.3.6.1.4.1.7165.2.1.44 NAME 'sambaIntegerOption' D
ESC 'An integer option' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.27 SINGLE-VALUE )
olcAttributeTypes: {32}( 1.3.6.1.4.1.7165.2.1.45 NAME 'sambaStringOption' DE
SC 'A string option' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115
.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {33}( 1.3.6.1.4.1.7165.2.1.46 NAME 'sambaStringListOption
' DESC 'A string list option' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1
466.115.121.1.15 )
olcAttributeTypes: {34}( 1.3.6.1.4.1.7165.2.1.53 NAME 'sambaTrustFlags' DESC
'Trust Password Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466
.115.121.1.26 )
olcAttributeTypes: {35}( 1.3.6.1.4.1.7165.2.1.58 NAME 'sambaMinPwdLength' DE
SC 'Minimal password length (default: 5)' EQUALITY integerMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {36}( 1.3.6.1.4.1.7165.2.1.59 NAME 'sambaPwdHistoryLength
' DESC 'Length of Password History Entries (default: 0 => off)' EQUALITY in
tegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {37}( 1.3.6.1.4.1.7165.2.1.60 NAME 'sambaLogonToChgPwd' D
ESC 'Force Users to logon for password change (default: 0 => off, 2 => on)'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {38}( 1.3.6.1.4.1.7165.2.1.61 NAME 'sambaMaxPwdAge' DESC
'Maximum password age, in seconds (default: -1 => never expire passwords)'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {39}( 1.3.6.1.4.1.7165.2.1.62 NAME 'sambaMinPwdAge' DESC
'Minimum password age, in seconds (default: 0 => allow immediate password c
hange)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-V
ALUE )
olcAttributeTypes: {40}( 1.3.6.1.4.1.7165.2.1.63 NAME 'sambaLockoutDuration'
DESC 'Lockout duration in minutes (default: 30, -1 => forever)' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {41}( 1.3.6.1.4.1.7165.2.1.64 NAME 'sambaLockoutObservati
onWindow' DESC 'Reset time after lockout in minutes (default: 30)' EQUALITY
integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {42}( 1.3.6.1.4.1.7165.2.1.65 NAME 'sambaLockoutThreshold
' DESC 'Lockout users after bad logon attempts (default: 0 => off)' EQUALIT
Y integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {43}( 1.3.6.1.4.1.7165.2.1.66 NAME 'sambaForceLogoff' DES
C 'Disconnect Users outside logon hours (default: -1 => off, 0 => on)' EQUA
LITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {44}( 1.3.6.1.4.1.7165.2.1.67 NAME 'sambaRefuseMachinePwd
Change' DESC 'Allow Machine Password changes (default: 0 => off)' EQUALITY
integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {45}( 1.3.6.1.4.1.7165.2.1.68 NAME 'sambaClearTextPasswor
d' DESC 'Clear text password (used for trusted domain passwords)' EQUALITY
octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {46}( 1.3.6.1.4.1.7165.2.1.69 NAME 'sambaPreviousClearTex
tPassword' DESC 'Previous clear text password (used for trusted domain pass
words)' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {47}( 1.3.6.1.4.1.7165.2.1.70 NAME 'sambaTrustType' DESC
'Type of trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE )
olcAttributeTypes: {48}( 1.3.6.1.4.1.7165.2.1.71 NAME 'sambaTrustAttributes'
DESC 'Trust attributes for a trusted domain' EQUALITY integerMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {49}( 1.3.6.1.4.1.7165.2.1.72 NAME 'sambaTrustDirection'
DESC 'Direction of a trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {50}( 1.3.6.1.4.1.7165.2.1.73 NAME 'sambaTrustPartner' DE
SC 'Fully qualified name of the domain with which a trust exists' EQUALITY
caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {51}( 1.3.6.1.4.1.7165.2.1.74 NAME 'sambaFlatName' DESC '
NetBIOS name of a domain' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.15{128} )
olcAttributeTypes: {52}( 1.3.6.1.4.1.7165.2.1.75 NAME 'sambaTrustAuthOutgoin
g' DESC 'Authentication information for the outgoing portion of a trust' EQ
UALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {53}( 1.3.6.1.4.1.7165.2.1.76 NAME 'sambaTrustAuthIncomin
g' DESC 'Authentication information for the incoming portion of a trust' EQ
UALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {54}( 1.3.6.1.4.1.7165.2.1.77 NAME 'sambaSecurityIdentifi
er' DESC 'SID of a trusted domain' EQUALITY caseIgnoreIA5Match SUBSTR caseE
xactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALU
E )
olcAttributeTypes: {55}( 1.3.6.1.4.1.7165.2.1.78 NAME 'sambaTrustForestTrust
Info' DESC 'Forest trust information for a trusted domain object' EQUALITY
caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {56}( 1.3.6.1.4.1.7165.2.1.79 NAME 'sambaTrustPosixOffset
' DESC 'POSIX offset of a trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1
466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {57}( 1.3.6.1.4.1.7165.2.1.80 NAME 'sambaSupportedEncrypt
ionTypes' DESC 'Supported encryption types of a trust' EQUALITY integerMatc
h SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' DESC 'S
amba 3.0 Auxilary SAM Account' SUP top AUXILIARY MUST ( uid $ sambaSID ) MA
Y ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $ sambaLogonTi
me $ sambaLogoffTime $ sambaKickoffTime $ sambaPwdCanChange $ sambaPwdMustC
hange $ sambaAcctFlags $ displayName $ sambaHomePath $ sambaHomeDrive $ sam
baLogonScript $ sambaProfilePath $ description $ sambaUserWorkstations $ sa
mbaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $ sambaBadPasswordCo
unt $ sambaBadPasswordTime $ sambaPasswordHistory $ sambaLogonHours ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.7165.2.2.4 NAME 'sambaGroupMapping' DESC
'Samba Group Mapping' SUP top AUXILIARY MUST ( gidNumber $ sambaSID $ samba
GroupType ) MAY ( displayName $ description $ sambaSIDList ) )
olcObjectClasses: {2}( 1.3.6.1.4.1.7165.2.2.14 NAME 'sambaTrustPassword' DES
C 'Samba Trust Password' SUP top STRUCTURAL MUST ( sambaDomainName $ sambaN
TPassword $ sambaTrustFlags ) MAY ( sambaSID $ sambaPwdLastSet ) )
olcObjectClasses: {3}( 1.3.6.1.4.1.7165.2.2.15 NAME 'sambaTrustedDomainPassw
ord' DESC 'Samba Trusted Domain Password' SUP top STRUCTURAL MUST ( sambaDo
mainName $ sambaSID $ sambaClearTextPassword $ sambaPwdLastSet ) MAY sambaP
reviousClearTextPassword )
olcObjectClasses: {4}( 1.3.6.1.4.1.7165.2.2.5 NAME 'sambaDomain' DESC 'Samba
Domain Information' SUP top STRUCTURAL MUST ( sambaDomainName $ sambaSID )
MAY ( sambaNextRid $ sambaNextGroupRid $ sambaNextUserRid $ sambaAlgorithm
icRidBase $ sambaMinPwdLength $ sambaPwdHistoryLength $ sambaLogonToChgPwd
$ sambaMaxPwdAge $ sambaMinPwdAge $ sambaLockoutDuration $ sambaLockoutObse
rvationWindow $ sambaLockoutThreshold $ sambaForceLogoff $ sambaRefuseMachi
nePwdChange ) )
olcObjectClasses: {5}( 1.3.6.1.4.1.7165.2.2.7 NAME 'sambaUnixIdPool' DESC 'P
ool for allocating UNIX uids/gids' SUP top AUXILIARY MUST ( uidNumber $ gid
Number ) )
olcObjectClasses: {6}( 1.3.6.1.4.1.7165.2.2.8 NAME 'sambaIdmapEntry' DESC 'M
apping from a SID to an ID' SUP top AUXILIARY MUST sambaSID MAY ( uidNumber
$ gidNumber ) )
olcObjectClasses: {7}( 1.3.6.1.4.1.7165.2.2.9 NAME 'sambaSidEntry' DESC 'Str
uctural Class for a SID' SUP top STRUCTURAL MUST sambaSID )
olcObjectClasses: {8}( 1.3.6.1.4.1.7165.2.2.10 NAME 'sambaConfig' DESC 'Samb
a Configuration Section' SUP top AUXILIARY MAY description )
olcObjectClasses: {9}( 1.3.6.1.4.1.7165.2.2.11 NAME 'sambaShare' DESC 'Samba
Share Section' SUP top STRUCTURAL MUST sambaShareName MAY description )
olcObjectClasses: {10}( 1.3.6.1.4.1.7165.2.2.12 NAME 'sambaConfigOption' DES
C 'Samba Configuration Option' SUP top STRUCTURAL MUST sambaOptionName MAY
( sambaBoolOption $ sambaIntegerOption $ sambaStringOption $ sambaStringLis
toption $ description ) )
olcObjectClasses: {11}( 1.3.6.1.4.1.7165.2.2.16 NAME 'sambaTrustedDomain' DE
SC 'Samba Trusted Domain Object' SUP top STRUCTURAL MUST cn MAY ( sambaTrus
tType $ sambaTrustAttributes $ sambaTrustDirection $ sambaTrustPartner $ sa
mbaFlatName $ sambaTrustAuthOutgoing $ sambaTrustAuthIncoming $ sambaSecuri
tyIdentifier $ sambaTrustForestTrustInfo $ sambaTrustPosixOffset $ sambaSup
portedEncryptionTypes ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 5c692e8a-7ddc-103b-8d45-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.760287Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 6d98bb57
dn: olcDatabase={-1}frontend
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
olcDatabase: {-1}frontend
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=extern
al,cn=auth manage by * break
olcAccess: {1}to dn.exact="" by * read
olcAccess: {2}to dn.base="cn=Subschema" by * read
olcSizeLimit: 500
structuralObjectClass: olcDatabaseConfig
entryUUID: 5c22f244-7ddc-103b-9ebc-c740e015b81b
creatorsName: cn=config
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.299952Z#000000#000#000000
modifiersName: cn=config
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,16 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 4503eefa
dn: olcDatabase={0}config
objectClass: olcDatabaseConfig
olcDatabase: {0}config
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=extern
al,cn=auth manage by * break
olcRootDN: cn=admin,cn=config
structuralObjectClass: olcDatabaseConfig
entryUUID: 5c248d0c-7ddc-103b-9ebd-c740e015b81b
creatorsName: cn=config
createTimestamp: 20210720192809Z
olcRootPW:: e1NTSEF9eGNpMDV6aExUSXhxL3k0NHYyVHlBR2p0bCsxblV1UHc=
entryCSN: 20210812002157.882687Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210812002157Z
@@ -0,0 +1,31 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 506f4bf6
dn: olcDatabase={1}mdb
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {1}mdb
olcDbDirectory: /var/lib/ldap
olcSuffix: dc=glauth,dc=com
olcLastMod: TRUE
olcRootDN: cn=admin,dc=glauth,dc=com
olcDbCheckpoint: 512 30
olcDbMaxSize: 1073741824
structuralObjectClass: olcMdbConfig
entryUUID: 5c2cdd40-7ddc-103b-9ec4-c740e015b81b
creatorsName: cn=admin,cn=config
createTimestamp: 20210720192809Z
olcDbIndex: uid eq
olcDbIndex: mail eq
olcDbIndex: memberOf eq
olcDbIndex: entryCSN eq
olcDbIndex: entryUUID eq
olcDbIndex: objectClass eq
olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by dn="cn
=serviceuser,cn=svcaccts,ou=users,dc=glauth,dc=com" write by anonymous auth
by * none
olcAccess: {1}to * by self write by dn="cn=serviceuser,cn=svcaccts,ou=users,
dc=glauth,dc=com" write by users read by * none
olcRootPW:: e1NTSEF9TzNabzVITEhzQXU1dkg5a0txNTlEVFhGclpPVDBPQUg=
entryCSN: 20210812002157.895969Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210812002157Z
@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 5273ae52
dn: olcOverlay={0}memberof
objectClass: olcOverlayConfig
objectClass: olcMemberOf
olcOverlay: {0}memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfUniqueNames
olcMemberOfMemberAD: uniqueMember
olcMemberOfMemberOfAD: memberOf
structuralObjectClass: olcMemberOf
entryUUID: 5c74c556-7ddc-103b-8d46-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.836237Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 a7700674
dn: olcOverlay={1}refint
objectClass: olcOverlayConfig
objectClass: olcRefintConfig
olcOverlay: {1}refint
olcRefintAttribute: owner
olcRefintAttribute: manager
olcRefintAttribute: uniqueMember
olcRefintAttribute: member
olcRefintAttribute: memberOf
structuralObjectClass: olcRefintConfig
entryUUID: 5c78d98e-7ddc-103b-8d47-8b35b6446851
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20210720192809Z
entryCSN: 20210720192809.862968Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20210720192809Z
@@ -0,0 +1,4 @@
export PREVIOUS_LDAP_TLS_CA_CRT_PATH=/container/service/slapd/assets/certs/ca.crt
export PREVIOUS_LDAP_TLS_CRT_PATH=/container/service/slapd/assets/certs/ldap.crt
export PREVIOUS_LDAP_TLS_KEY_PATH=/container/service/slapd/assets/certs/ldap.key
export PREVIOUS_LDAP_TLS_DH_PARAM_PATH=/container/service/slapd/assets/certs/dhparam.pem
Binary file not shown.
Binary file not shown.
+107
View File
@@ -0,0 +1,107 @@
package config
import "time"
// config file
type Backend struct {
BaseDN string
Datastore string
Insecure bool // For LDAP and owncloud backend only
Servers []string // For LDAP and owncloud backend only
NameFormat string
GroupFormat string
SSHKeyAttr string
UseGraphAPI bool // For ownCloud backend only
Plugin string // Path to plugin library, for plugin backend only
PluginHandler string // Name of plugin's main handler function
Database string // For Database backends only
AnonymousDSE bool // For Config and Database backends only
}
type Helper struct {
Enabled bool
BaseDN string
Datastore string
Plugin string // Path to plugin library, for plugin backend only
PluginHandler string // Name of plugin's main handler function
Database string // For MySQL backend only TODO REname to match plugin
}
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 Behaviors struct {
LimitFailedBinds bool
NumberOfFailedBinds int
PeriodOfFailedBinds time.Duration
BlockFailedBindsFor time.Duration
PruneSourceTableEvery time.Duration
PruneSourcesOlderThan time.Duration
}
type User struct {
Name string
OtherGroups []int
PassSHA256 string
PassBcrypt string
PassAppSHA256 []string
PassAppBcrypt []string
PrimaryGroup int
SSHKeys []string
OTPSecret string
Yubikey string
Disabled bool
UnixID int // TODO: remove after deprecating UnixID on User and Group
UIDNumber int
Mail string
LoginShell string
GivenName string
SN string
Homedir string
}
type Group struct {
Name string
UnixID int // TODO: remove after deprecating UnixID on User and Group
GIDNumber int
IncludeGroups []int
}
type Config struct {
API API
Backend Backend // Deprecated
Backends []Backend
Helper Helper
Behaviors Behaviors
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
}
+50
View File
@@ -0,0 +1,50 @@
package frontend
import (
"context"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger logr.Logger
Config *config.API
Context context.Context
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.API) Option {
return func(o *Options) {
o.Config = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
+70
View File
@@ -0,0 +1,70 @@
package frontend
import (
"bytes"
"fmt"
"io"
"net/http"
"strings"
"github.com/glauth/glauth/pkg/assets"
"github.com/go-logr/logr"
)
// RunAPI provides a basic REST API
func RunAPI(opts ...Option) {
options := newOptions(opts...)
log := options.Logger
cfg := options.Config
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.V(6).Info("Web", "path", r.URL.Path)
if r.URL.Path != "/" {
log.V(6).Info("Web 404")
http.NotFound(w, r)
return
}
pageTemplate, err := assets.Asset("assets/index.html")
if err != nil {
log.Error(err, "Error with HTTP server template asset")
return
}
fmt.Fprintf(w, string(pageTemplate))
})
http.HandleFunc("/assets/", webStaticHandler(log))
if cfg.TLS {
log.V(3).Info("Starting HTTPS server", "address", cfg.Listen)
err := http.ListenAndServeTLS(cfg.Listen, cfg.Cert, cfg.Key, nil)
if err != nil {
log.Error(err, "Error starting HTTPS server")
return
}
} else {
log.V(3).Info("Starting HTTP server", "address", cfg.Listen)
err := http.ListenAndServe(cfg.Listen, nil)
if err != nil {
log.Error(err, "Error starting HTTP server")
return
}
}
}
// webStaticHandler serves embedded static web files (js&css)
func webStaticHandler(log logr.Logger) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
assetPath := r.URL.Path[1:]
staticAsset, err := assets.Asset(assetPath)
if err != nil {
log.Error(err, "Cannot access asset")
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))
}
}
+186
View File
@@ -0,0 +1,186 @@
// Package gologgingr implements github.com/go-logr/logr.Logger in terms of
// github.com/op/go-logging.
package gologgingr
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"github.com/go-logr/logr"
)
// The global verbosity level. See SetVerbosity().
var globalVerbosity int = 0
// SetVerbosity sets the global level against which all info logs will be
// compared. If this is greater than or equal to the "V" of the logger, the
// message will be logged. A higher value here means more logs will be written.
// The previous verbosity value is returned. This is not concurrent-safe -
// callers must be sure to call it from only one goroutine.
func SetVerbosity(v int) int {
old := globalVerbosity
globalVerbosity = v
return old
}
// New returns a logr.Logger which is implemented by Go's standard
// log package, or something like it. See New for details.
func New(opts ...Option) logr.Logger {
options := newOptions(opts...)
l := logger{
llog: options.Logger,
level: 0,
prefix: "",
values: nil,
}
return l
}
// LeveledLogger is the subset of the Go stdlib log.Logger API that is needed for
// this adapter.
type LeveledLogger interface {
Fatal(args ...interface{})
Error(args ...interface{})
Warning(args ...interface{})
Notice(args ...interface{})
Info(args ...interface{})
Debug(args ...interface{})
}
type logger struct {
llog LeveledLogger
level int
prefix string
values []interface{}
depth int
}
func (l logger) clone() logger {
out := l
l.values = copySlice(l.values)
return out
}
func copySlice(in []interface{}) []interface{} {
out := make([]interface{}, len(in))
copy(out, in)
return out
}
// Magic string for intermediate frames that we should ignore.
const autogeneratedFrameName = "<autogenerated>"
type kvPair struct {
key string
val interface{}
}
func flatten(kvList ...interface{}) string {
keys := make([]string, 0, len(kvList))
vals := make(map[string]interface{}, len(kvList))
for i := 0; i < len(kvList); i += 2 {
k, ok := kvList[i].(string)
if !ok {
panic(fmt.Sprintf("key is not a string: %s", pretty(kvList[i])))
}
var v interface{}
if i+1 < len(kvList) {
v = kvList[i+1]
}
keys = append(keys, k)
vals[k] = v
}
sort.Strings(keys)
buf := bytes.Buffer{}
for i, k := range keys {
v := vals[k]
if i > 0 {
buf.WriteRune(' ')
}
buf.WriteString(pretty(k))
buf.WriteString("=")
buf.WriteString(pretty(v))
}
return buf.String()
}
func pretty(value interface{}) string {
jb, _ := json.Marshal(value)
return string(jb)
}
func (l logger) Info(msg string, kvList ...interface{}) {
if l.Enabled() {
lvlStr := flatten("level", l.level)
msgStr := flatten("msg", msg)
fixedStr := flatten(l.values...)
userStr := flatten(kvList...)
l.output(fmt.Sprintln(l.prefix, lvlStr, msgStr, fixedStr, userStr))
}
}
func (l logger) Enabled() bool {
return globalVerbosity >= l.level
}
func (l logger) Error(err error, msg string, kvList ...interface{}) {
msgStr := flatten("msg", msg)
var loggableErr interface{}
if err != nil {
loggableErr = err.Error()
}
errStr := flatten("error", loggableErr)
fixedStr := flatten(l.values...)
userStr := flatten(kvList...)
l.output(fmt.Sprintln(l.prefix, errStr, msgStr, fixedStr, userStr))
}
func (l logger) output(s string) {
switch l.level {
case 0:
l.llog.Error(s)
case 1:
l.llog.Error(s)
case 2:
l.llog.Warning(s)
case 3:
l.llog.Notice(s)
case 4:
l.llog.Info(s)
case 5:
l.llog.Info(s)
default:
l.llog.Debug(s)
}
}
func (l logger) V(level int) logr.InfoLogger {
new := l.clone()
new.level = level
return new
}
// WithName returns a new logr.Logger with the specified name appended. stdr
// uses '/' characters to separate name elements. Callers should not pass '/'
// in the provided name string, but this library does not actually enforce that.
func (l logger) WithName(name string) logr.Logger {
new := l.clone()
if len(l.prefix) > 0 {
new.prefix = l.prefix + "/"
}
new.prefix += name
return new
}
func (l logger) WithValues(kvList ...interface{}) logr.Logger {
new := l.clone()
new.values = append(new.values, kvList...)
return new
}
var _ logr.Logger = logger{}
var _ logr.InfoLogger = logger{}
+27
View File
@@ -0,0 +1,27 @@
package gologgingr
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger LeveledLogger
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val LeveledLogger) Option {
return func(o *Options) {
o.Logger = val
}
}
+325
View File
@@ -0,0 +1,325 @@
package handler
import (
"fmt"
"net"
"regexp"
"sort"
"strings"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
)
type configHandler struct {
backend config.Backend
log logr.Logger
cfg *config.Config
yubikeyAuth *yubigo.YubiAuth
ldohelper LDAPOpsHelper
attmatcher *regexp.Regexp
}
// NewConfigHandler creates a new config backed handler
func NewConfigHandler(opts ...Option) Handler {
options := newOptions(opts...)
handler := configHandler{
backend: options.Backend,
log: options.Logger,
cfg: options.Config, // TODO only used to access Users and Groups, move that to dedicated options
yubikeyAuth: options.YubiAuth,
ldohelper: options.LDAPHelper,
attmatcher: configattributematcher,
}
return handler
}
func (h configHandler) GetBackend() config.Backend {
return h.backend
}
func (h configHandler) GetLog() logr.Logger {
return h.log
}
func (h configHandler) GetCfg() *config.Config {
return h.cfg
}
func (h configHandler) GetYubikeyAuth() *yubigo.YubiAuth {
return h.yubikeyAuth
}
// Bind implements a bind request against the config file
func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
}
// Search implements a search request against the config file
func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
return h.ldohelper.Search(h, bindDN, searchReq, conn)
}
// Add is not supported for a static config file
func (h configHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Modify is not supported for a static config file
func (h configHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Delete is not supported for a static config file
func (h configHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h configHandler) FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error) {
user := config.User{}
found := false
for _, u := range h.cfg.Users {
if searchByUPN {
if strings.EqualFold(u.Mail, userName) {
found = true
user = u
}
} else {
if strings.EqualFold(u.Name, userName) {
found = true
user = u
}
}
}
return found, user, nil
}
func (h configHandler) FindGroup(groupName string) (f bool, g config.Group, err error) {
// TODO Does g get erased, and above does u get erased?
// TODO and what about f?
group := config.Group{}
found := false
for _, g := range h.cfg.Groups {
if strings.EqualFold(g.Name, groupName) {
found = true
group = g
}
}
return found, group, nil
}
func (h configHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
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.UIDNumber)}})
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: "userPrincipalName", 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.backend.SSHKeyAttr, Values: u.SSHKeys})
}
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
return entries, nil
}
func (h configHandler) FindPosixGroups() (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
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.GIDNumber)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uniqueMember", Values: h.getGroupMembers(g.GIDNumber)})
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: h.getGroupMemberIDs(g.GIDNumber)})
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.backend.GroupFormat, h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
return entries, nil
}
// Close does not actually close anything, because the config data is kept in memory
func (h configHandler) Close(boundDn string, conn net.Conn) error {
stats.Frontend.Add("closes", 1)
return nil
}
func (h configHandler) getGroupMembers(gid int) []string {
members := make(map[string]bool)
for _, u := range h.cfg.Users {
if u.PrimaryGroup == gid {
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
} else {
for _, othergid := range u.OtherGroups {
if othergid == gid {
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
}
}
}
}
for _, g := range h.cfg.Groups {
if gid == g.GIDNumber {
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.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid {
h.log.V(2).Info("Ignoring myself as included group", "groupid", 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.GIDNumber == gid {
dn := fmt.Sprintf("cn=%s,%s=groups,%s", g.Name, h.backend.GroupFormat, h.backend.BaseDN)
groups[dn] = true
}
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid && g.GIDNumber != gid {
includegroupdns := h.getGroupDNs([]int{g.GIDNumber})
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.GIDNumber == gid {
return g.Name
}
}
return ""
}
+35
View File
@@ -0,0 +1,35 @@
package handler
import (
"github.com/glauth/glauth/pkg/config"
"github.com/nmcclain/ldap"
)
type HelperMaker interface {
FindUser(userName string, searchByUPN bool) (bool, config.User, error)
FindGroup(groupName string) (bool, config.Group, error)
}
// Handler is the common interface for all datastores
type Handler interface {
// read support
ldap.Binder
ldap.Searcher
ldap.Closer
// write support
ldap.Adder
ldap.Modifier // Note: modifying eg the uid or cn might change the dn because the hierarchy is determined by the backend
ldap.Deleter
// helper
HelperMaker
}
// TODO When I grow up, I want to handle pointers same as I would in C
// and not need a counter because I would not allocate statically
// but use idiomatic slicing instead
type HandlerWrapper struct {
Handlers []Handler
Count *int
}
+495
View File
@@ -0,0 +1,495 @@
package handler
import (
"crypto/sha256"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/pquerna/otp/totp"
)
// global matcher
var ldapattributematcher = regexp.MustCompile(`(?i)(?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)`)
type ldapHandler struct {
backend config.Backend
handlers HandlerWrapper
doPing chan bool
log logr.Logger
lock *sync.Mutex // for sessions and servers
sessions map[string]ldapSession
servers []ldapBackend
helper Handler
attm *regexp.Regexp
}
// global lock for ldapHandler sessions & servers manipulation
var ldaplock sync.Mutex
type ldapSession struct {
id string
c net.Conn
ldap *ldap.Conn
}
type ldapBackendStatus int
const (
Down ldapBackendStatus = iota
Up
)
type ldapBackend struct {
Scheme string
Hostname string
Port int
Status ldapBackendStatus
Ping time.Duration
}
func NewLdapHandler(opts ...Option) Handler {
options := newOptions(opts...)
handler := ldapHandler{ // set non-zero-value defaults here
backend: options.Backend,
handlers: options.Handlers,
sessions: make(map[string]ldapSession),
doPing: make(chan bool),
log: options.Logger,
helper: options.Helper,
lock: &ldaplock,
attm: ldapattributematcher,
}
// parse LDAP URLs
for _, ldapurl := range handler.backend.Servers {
l, err := parseURL(ldapurl)
if err != nil {
handler.log.Error(err, "could not parse url")
os.Exit(1)
}
handler.servers = append(handler.servers, l)
}
// test server connectivity before listening, then keep it updated
handler.monitorServers()
return handler
}
//
func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
h.log.V(6).Info("Bind request", "binddn", bindDN, "src", conn.RemoteAddr())
// if h.helper != nil {
if true {
lowerBindDN := strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.backend.BaseDN)
parts := strings.Split(strings.TrimSuffix(lowerBindDN, baseDN), ",")
userName := strings.TrimPrefix(parts[0], h.backend.NameFormat+"=")
validotp := false
// Find the user
// We are going to go through all backends and ask
// until we find our user or die of boredom.
user := config.User{}
found := false
for i, handler := range h.handlers.Handlers {
found, user, _ = handler.FindUser(userName, false)
if found {
break
}
if i >= *h.handlers.Count {
break
}
}
if !found {
validotp = true
} else {
if len(user.OTPSecret) == 0 {
validotp = true
} else {
if len(bindSimplePw) > 6 {
otp := bindSimplePw[len(bindSimplePw)-6:]
bindSimplePw = bindSimplePw[:len(bindSimplePw)-6]
validotp = totp.Validate(otp, user.OTPSecret)
}
}
}
if !validotp {
h.log.V(6).Info(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultInvalidCredentials, nil
}
}
stats.Frontend.Add("bind_reqs", 1)
s, err := h.getSession(conn)
if err != nil {
stats.Frontend.Add("bind_ldapSession_errors", 1)
h.log.V(6).Info("could not get session", "binddn", bindDN, "src", conn.RemoteAddr(), "error", err)
return ldap.LDAPResultOperationsError, err
}
if err := s.ldap.Bind(bindDN, bindSimplePw); err != nil {
stats.Frontend.Add("bind_errors", 1)
h.log.V(6).Info("invalid creds", "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultInvalidCredentials, nil
}
stats.Frontend.Add("bind_successes", 1)
h.log.V(6).Info("bind success", "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
//
func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
wantAttributes := true
wantTypesOnly := false
h.log.V(6).Info("Search request", "binddn", boundDN, "src", conn.RemoteAddr(), "filter", searchReq.Filter)
// "1.1" has special meaning: it does what an empty attribute list would do
// if it didn't already mean "return all attributes"
if len(searchReq.Attributes) == 1 && searchReq.Attributes[0] == "1.1" {
wantAttributes = false
searchReq.Attributes = searchReq.Attributes[:0]
}
// TypesOnly cannot be true: if it were, glauth would not be able to
// match the returned valuea against the query
if searchReq.TypesOnly == true {
wantTypesOnly = true
searchReq.TypesOnly = false
}
stats.Frontend.Add("search_reqs", 1)
s, err := h.getSession(conn)
if err != nil {
stats.Frontend.Add("search_ldapSession_errors", 1)
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, nil
}
search := ldap.NewSearchRequest(
searchReq.BaseDN,
searchReq.Scope,
searchReq.DerefAliases,
searchReq.SizeLimit,
searchReq.TimeLimit,
searchReq.TypesOnly,
searchReq.Filter,
searchReq.Attributes,
searchReq.Controls,
)
h.log.V(6).Info("Search request to backend", "request", search)
sr, err := s.ldap.Search(search)
h.log.V(6).Info("Backend Search result", "result", sr)
if !wantAttributes {
h.log.V(6).Info("AP: Search Info", "type", "No attributes")
for _, entry := range sr.Entries {
entry.Attributes = entry.Attributes[:0]
}
}
if wantTypesOnly {
h.log.V(6).Info("AP: Search Info", "type", "Types only")
for _, entry := range sr.Entries {
for _, attribute := range entry.Attributes {
attribute.Values = attribute.Values[:0]
}
}
}
// WART used to debug when testing special cases against
// glauth acting as a backend, where it may have
// the same workaround thus hiding the issue
/*
for _, entry := range sr.Entries {
for _, attribute := range entry.Attributes {
if attribute.Name == "objectclass" {
attribute.Name = "bogus"
}
}
}
*/
// If our original attribute is not present, either because:
// 1-This is a root query
// 2-We were asked not to return attributes
// 3-We were asked not to return values
// then we re-insert the correct values in there.
if searchReq.Scope == 0 && searchReq.BaseDN == "" {
h.log.V(6).Info("AP: Search Info", "type", "Root search detected")
}
filters := h.buildReqAttributesList(searchReq.Filter, []string{})
for _, filter := range filters {
attbits := h.attm.FindStringSubmatch(filter)
for _, entry := range sr.Entries {
foundattname := false
for _, attribute := range entry.Attributes {
if strings.ToLower(attribute.Name) == strings.ToLower(attbits[1]) {
foundattname = true
if len(attbits[2]) == 0 {
attribute.Values = []string{attbits[2]}
}
break
}
}
if !foundattname {
entry.Attributes = append(entry.Attributes, &ldap.EntryAttribute{Name: attbits[1], Values: []string{attbits[2]}})
}
}
}
ssr := ldap.ServerSearchResult{
Entries: sr.Entries,
Referrals: sr.Referrals,
Controls: sr.Controls,
}
h.log.V(6).Info("Frontend Search result", "result", ssr)
if err != nil {
e := err.(*ldap.Error)
h.log.V(6).Info("Search Err", "error", err)
stats.Frontend.Add("search_errors", 1)
ssr.ResultCode = ldap.LDAPResultCode(e.ResultCode)
return ssr, err
}
stats.Frontend.Add("search_successes", 1)
h.log.V(6).Info("AP: Search OK", "filter", search.Filter, "numentries", len(ssr.Entries))
return ssr, nil
}
func (h ldapHandler) buildReqAttributesList(filter string, filters []string) []string {
maxp := len(filter)
start := -1
descended := false
for p, c := range filter {
if c == '(' {
if p+1 < maxp {
start = p + 1
}
} else if c == ')' {
if start > -1 {
descended = true
filters = h.buildReqAttributesList(filter[start:p], filters)
}
start = -1
}
}
if !descended {
filters = append(filters, filter)
}
return filters
}
// Add is not yet supported for the ldap backend
func (h ldapHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Modify is not yet supported for the ldap backend
func (h ldapHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Delete is not yet supported for the ldap backend
func (h ldapHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h ldapHandler) FindUser(userName string, searchByUPN bool) (found bool, user config.User, err error) {
return false, config.User{}, nil
}
func (h ldapHandler) FindGroup(groupName string) (found bool, group config.Group, err error) {
return false, config.Group{}, nil
}
func (h ldapHandler) 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
}
// monitorServers tests server connectivity before listening, then keeps it updated
func (h *ldapHandler) monitorServers() {
err := h.ping()
if err != nil {
h.log.Error(err, "could not ping server")
os.Exit(1)
// TODO return error
}
go func() {
for {
select {
case <-h.doPing:
h.log.V(3).Info("doPing requested due to server failure")
err = h.ping()
if err != nil {
h.log.Error(err, "could not ping server")
os.Exit(1)
// TODO return error
}
case <-time.NewTimer(60 * time.Second).C:
h.log.V(6).Info("doPing after timeout")
err = h.ping()
if err != nil {
h.log.Error(err, "could not ping server")
os.Exit(1)
// TODO return error
}
}
}
}()
}
//
func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
id := connID(conn)
h.lock.Lock()
s, ok := h.sessions[id] // use server connection if it exists
h.lock.Unlock()
if !ok { // open a new server connection if not
var l *ldap.Conn
server, err := h.getBestServer() // pick the best server
if err != nil {
return ldapSession{}, err
}
dest := fmt.Sprintf("%s:%d", server.Hostname, server.Port)
if server.Scheme == "ldaps" {
tlsCfg := &tls.Config{}
if h.backend.Insecure {
tlsCfg.InsecureSkipVerify = true
}
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
} else if server.Scheme == "ldap" {
l, err = ldap.Dial("tcp", dest)
}
if err != nil {
select {
case h.doPing <- true: // non-blocking send
default:
}
return ldapSession{}, err
}
s = ldapSession{id: id, c: conn, ldap: l}
h.lock.Lock()
h.sessions[s.id] = s
h.lock.Unlock()
}
return s, nil
}
//
func (h ldapHandler) ping() error {
healthy := false
for k, s := range h.servers {
var l *ldap.Conn
var err error
dest := fmt.Sprintf("%s:%d", s.Hostname, s.Port)
start := time.Now()
if h.servers[0].Scheme == "ldaps" {
tlsCfg := &tls.Config{}
if h.backend.Insecure {
tlsCfg.InsecureSkipVerify = true
}
l, err = ldap.DialTLS("tcp", dest, tlsCfg)
} else if h.servers[0].Scheme == "ldap" {
l, err = ldap.Dial("tcp", dest)
}
elapsed := time.Since(start)
h.lock.Lock()
if err != nil || l == nil {
h.log.V(1).Info("Server ping failed", "hostname", s.Hostname, "port", s.Port, "error", err)
h.servers[k].Ping = 0
h.servers[k].Status = Down
} else {
healthy = true
h.servers[k].Ping = elapsed
h.servers[k].Status = Up
l.Close() // prank caller
}
h.lock.Unlock()
}
h.log.V(6).Info("Server health", "servers", h.servers)
b, err := json.Marshal(h.servers)
if err != nil {
h.log.V(1).Info("Error encoding tail data", "error", err)
}
stats.Backend.Set("servers", stats.Stringer(string(b)))
if healthy == false {
return fmt.Errorf("No healthy servers")
}
return nil
}
//
func (h ldapHandler) getBestServer() (ldapBackend, error) {
favorite := ldapBackend{}
forever, err := time.ParseDuration("30m")
if err != nil {
return ldapBackend{}, err
}
bestping := forever
for _, s := range h.servers {
if s.Status == Up && s.Ping < bestping {
favorite = s
bestping = s.Ping
}
}
if bestping == forever {
return ldapBackend{}, fmt.Errorf("No healthy servers found")
}
h.log.V(6).Info("Best server", "favorite", favorite)
return favorite, nil
}
// helper functions
func connID(conn net.Conn) string {
h := sha256.New()
h.Write([]byte(conn.LocalAddr().String() + conn.RemoteAddr().String()))
sha := fmt.Sprintf("% x", h.Sum(nil))
return string(sha)
}
func parseURL(ldapurl string) (ldapBackend, error) {
u, err := url.Parse(ldapurl)
if err != nil {
return ldapBackend{}, err
}
var port int
if u.Scheme == "ldaps" {
port = 636
} else if u.Scheme == "ldap" {
port = 389
} else {
return ldapBackend{}, fmt.Errorf("Unknown LDAP scheme: %s", u.Scheme)
}
parts := strings.Split(u.Host, ":")
hostname := parts[0]
if len(parts) > 1 {
port, err = strconv.Atoi(parts[1])
if err != nil {
return ldapBackend{}, err
}
}
return ldapBackend{Scheme: u.Scheme, Hostname: hostname, Port: port}, nil
}
+461
View File
@@ -0,0 +1,461 @@
package handler
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/bcrypt"
)
var configattributematcher = regexp.MustCompile(`(?i)\((?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)\)`)
var emailmatcher = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
type LDAPOpsHandler interface {
GetBackend() config.Backend
GetLog() logr.Logger
GetCfg() *config.Config
GetYubikeyAuth() *yubigo.YubiAuth
FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error)
FindGroup(groupName string) (f bool, g config.Group, err error)
FindPosixAccounts() (entrylist []*ldap.Entry, err error)
FindPosixGroups() (entrylist []*ldap.Entry, err error)
}
type failedBind struct {
ts time.Time
}
type sourceInfo struct {
lastSeen time.Time
failures chan failedBind
waitUntil time.Time
}
type LDAPOpsHelper struct {
sources map[string]*sourceInfo
nextPruning time.Time
}
func NewLDAPOpsHelper() LDAPOpsHelper {
helper := LDAPOpsHelper{
sources: make(map[string]*sourceInfo),
nextPruning: time.Now(),
}
return helper
}
func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
if l.isInTimeout(h, conn) {
return ldap.LDAPResultUnwillingToPerform, nil
}
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.GetBackend().BaseDN)
h.GetLog().V(6).Info("Bind request", "binddn", bindDN, "basedn", h.GetBackend().BaseDN, "src", conn.RemoteAddr())
stats.Frontend.Add("bind_reqs", 1)
// Special Case: bind as anonymous
if bindDN == "" && bindSimplePw == "" {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().V(6).Info("Anonymous Bind success", "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
var user config.User
// Special Case: bind using UPN
// Not using mail.ParseAddress/1 because we would allow incorrectly formatted UPNs
if emailmatcher.MatchString(bindDN) {
var foundUser bool // = false
foundUser, user, _ = h.FindUser(bindDN, true)
if !foundUser {
h.GetLog().V(2).Info("User not found", "userprincipalname", bindDN)
return ldap.LDAPResultInvalidCredentials, nil
}
} else {
// parse the bindDN - ensure that the bindDN ends with the BaseDN
if !strings.HasSuffix(bindDN, baseDN) {
h.GetLog().V(2).Info("BindDN not part of our BaseDN", "binddn", bindDN, "basedn", h.GetBackend().BaseDN)
// h.GetLog().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.GetBackend().NameFormat+"=")
} else if len(parts) == 2 {
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormat+"=")
groupName = strings.TrimPrefix(parts[1], h.GetBackend().GroupFormat+"=")
} else {
h.GetLog().V(2).Info("BindDN should have only one or two parts", "binddn", bindDN, "numparts", len(parts))
return ldap.LDAPResultInvalidCredentials, nil
}
// find the user
var foundUser bool // = false
foundUser, user, _ = h.FindUser(userName, false)
if !foundUser {
h.GetLog().V(2).Info("User not found", "username", userName)
return ldap.LDAPResultInvalidCredentials, nil
}
// find the group
var group config.Group // = nil
var foundGroup bool // = false
if groupName != "" {
foundGroup, group, _ = h.FindGroup(groupName)
if !foundGroup {
h.GetLog().V(2).Info("Group not found", "groupname", groupName)
return ldap.LDAPResultInvalidCredentials, nil
}
}
// validate group membership
if foundGroup {
if user.PrimaryGroup != group.GIDNumber {
h.GetLog().V(2).Info("primary group mismatch", "username", userName, "primarygroup", user.PrimaryGroup, "groupid", group.GIDNumber)
return ldap.LDAPResultInvalidCredentials, nil
}
}
}
validotp := false
if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {
validotp = true
}
if len(user.Yubikey) > 0 && h.GetYubikeyAuth() != 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.GetYubikeyAuth().Verify(otp)
if ok {
validotp = true
}
}
}
}
// Store the full bind password provided before possibly modifying
// in the otp check
untouchedBindSimplePw := 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
if user.PassAppBcrypt != nil {
for index, appPw := range user.PassAppBcrypt {
decoded, err := hex.DecodeString(appPw)
if err != nil {
h.GetLog().V(6).Info("invalid app credentials", "incorrect stored hash", "(omitted)")
} else {
if bcrypt.CompareHashAndPassword(decoded, []byte(untouchedBindSimplePw)) == nil {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
}
}
}
if user.PassAppSHA256 != nil {
hashFull := sha256.New()
hashFull.Write([]byte(untouchedBindSimplePw))
for index, appPw := range user.PassAppSHA256 {
if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
h.GetLog().V(6).Info("Attempt to bind app pw failed", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
} else {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
}
}
// Then ensure the OTP is valid before checking
if !validotp {
h.GetLog().V(2).Info("invalid OTP token", "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultInvalidCredentials, nil
}
// Now, check the pasword hash
if user.PassBcrypt != "" {
decoded, err := hex.DecodeString(user.PassBcrypt)
if err != nil {
h.GetLog().V(2).Info("invalid credentials", "incorrect stored hash", "(omitted)")
return ldap.LDAPResultInvalidCredentials, nil
}
if bcrypt.CompareHashAndPassword(decoded, []byte(bindSimplePw)) != nil {
h.GetLog().V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
l.maybePutInTimeout(h, conn, true)
return ldap.LDAPResultInvalidCredentials, nil
}
}
if user.PassSHA256 != "" {
hash := sha256.New()
hash.Write([]byte(bindSimplePw))
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
h.GetLog().V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
l.maybePutInTimeout(h, conn, true)
return ldap.LDAPResultInvalidCredentials, nil
}
}
stats.Frontend.Add("bind_successes", 1)
h.GetLog().V(6).Info("Bind success", "binddn", bindDN, "src", conn.RemoteAddr())
return ldap.LDAPResultSuccess, nil
}
func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
if l.isInTimeout(h, conn) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultUnwillingToPerform}, fmt.Errorf("Source is in a timeout")
}
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower(h.GetBackend().BaseDN)
delimitedBaseDN := "," + baseDN
searchBaseDN := strings.ToLower(searchReq.BaseDN)
// What if this user was bound using their UPN? We still want to enforce baseDN etc so we
// have to rewire them to their original DN which is of course a waste of cycles.
// TODO Down the road we would want to perform lightweight memoization of DNs to UPNs
if emailmatcher.MatchString(bindDN) {
foundUser, userInfo, _ := h.FindUser(bindDN, true)
if !foundUser {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: UPN %s could not be resolved to a DN", bindDN)
}
// cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
bindDN = fmt.Sprintf("cn=%s,%s", userInfo.Name, baseDN)
}
h.GetLog().V(6).Info("Search request", "binddn", bindDN, "basedn", baseDN, "searchbasedn", searchBaseDN, "src", conn.RemoteAddr(), "scope", searchReq.Scope, "filter", searchReq.Filter)
stats.Frontend.Add("search_reqs", 1)
anonymous := len(bindDN) < 1
// special case: querying the root DSE
if searchReq.Scope == 0 && (searchBaseDN == "" || searchBaseDN == baseDN) {
if anonymous && !h.GetBackend().AnonymousDSE {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Root DSE Search Error: Anonymous BindDN not allowed %s", bindDN)
}
h.GetLog().V(6).Info("Search request", "special case", "root DSE")
entries := []*ldap.Entry{}
attrs := []*ldap.EntryAttribute{}
// unfortunately, objectClass is not to be included so we will respect that
// attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"*"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedSASLMechanisms", Values: []string{}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedLDAPVersion", Values: []string{"3"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedControl", Values: []string{}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "supportedCapabilities", Values: []string{}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "subschemaSubentry", Values: []string{"cn=schema"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "serverName", Values: []string{"unknown"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "namingContexts", Values: []string{baseDN}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "defaultNamingContext", Values: []string{baseDN}})
attrs = l.collectRequestedAttributesBack(attrs, searchReq)
entries = append(entries, &ldap.Entry{DN: searchBaseDN, Attributes: attrs})
stats.Frontend.Add("search_successes", 1)
h.GetLog().V(6).Info("AP: Root Search OK", "filter", searchReq.Filter)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
// validate the user is authenticated and has appropriate access
if anonymous {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", bindDN)
}
if searchBaseDN == "cn=schema" {
h.GetLog().V(6).Info("Search request", "special case", "schema discovery")
entries := []*ldap.Entry{}
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{"schema"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "hasSubordinates", Values: []string{"false"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "modifiersName", Values: []string{"cn=Directory Manager"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "modifyTimeStamp", Values: []string{"Mar 8, 2021, 12:46:29 PM PST (20210308204629Z)"}})
// Iterate through schema attributes provided in schema/ directory
filenames, _ := ioutil.ReadDir("schema")
for _, filename := range filenames {
file, err := os.Open(filepath.Join("schema", filename.Name()))
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Schema Error: attribute %s cannot be read", filename)
}
defer file.Close()
values := []string{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
values = append(values, line)
}
attrs = append(attrs, &ldap.EntryAttribute{Name: filename.Name(), Values: values})
}
attrs = l.collectRequestedAttributesBack(attrs, searchReq)
entries = append(entries, &ldap.Entry{DN: searchBaseDN, Attributes: attrs})
stats.Frontend.Add("search_successes", 1)
h.GetLog().V(6).Info("AP: Schema Discovery OK", "filter", searchReq.Filter)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
if !strings.HasSuffix(bindDN, delimitedBaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", bindDN, h.GetBackend().BaseDN)
}
if !strings.HasSuffix(searchBaseDN, h.GetBackend().BaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: search BaseDN %s is not in our BaseDN %s", searchBaseDN, h.GetBackend().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":
posixentries, err := h.FindPosixGroups()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: Unable to retrieve data [%s]", err.Error())
}
entries = append(entries, posixentries...)
case "posixaccount", "shadowaccount", "":
posixentries, err := h.FindPosixAccounts()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: Unable to retrieve data [%s]", err.Error())
}
entries = append(entries, posixentries...)
}
stats.Frontend.Add("search_successes", 1)
h.GetLog().V(6).Info("AP: Search OK", "filter", searchReq.Filter)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
// If your query is for, say 'objectClass', then our LDAP
// library will weed out this entry since it does *not* contain an objectclass attribute
// so we are going to re-inject it to keep the LDAP library happy
func (l LDAPOpsHelper) collectRequestedAttributesBack(attrs []*ldap.EntryAttribute, searchReq ldap.SearchRequest) []*ldap.EntryAttribute {
attbits := configattributematcher.FindStringSubmatch(searchReq.Filter)
if len(attbits) == 3 {
foundattname := false
for _, attr := range attrs {
if strings.ToLower(attr.Name) == strings.ToLower(attbits[1]) {
foundattname = true
break
}
}
// the ugly hack: we are going to pretend that the requested attribute is in there
if !foundattname {
attrs = append(attrs, &ldap.EntryAttribute{Name: attbits[1], Values: []string{attbits[2]}})
}
}
return attrs
}
// return true if we should not process the current operation
func (l LDAPOpsHelper) isInTimeout(handler LDAPOpsHandler, conn net.Conn) bool {
cfg := handler.GetCfg()
if !cfg.Behaviors.LimitFailedBinds {
return false
}
remoteAddr := l.getAddr(conn)
now := time.Now()
info, ok := l.sources[remoteAddr]
if !ok {
l.sources[remoteAddr] = &sourceInfo{
lastSeen: now,
failures: make(chan failedBind, cfg.Behaviors.NumberOfFailedBinds),
waitUntil: now,
}
handler.GetLog().V(6).Info("CFRDBG: Created Struct", "remoteAddr", remoteAddr)
return false
}
// update so that this source does not get pruned
info.lastSeen = now
// if we are in a time out...
if cfg.Behaviors.LimitFailedBinds && info.waitUntil.After(now) {
handler.GetLog().V(6).Info("CFRDBG: Time Out")
return true
}
return false
}
func (l LDAPOpsHelper) maybePutInTimeout(handler LDAPOpsHandler, conn net.Conn, noteFailure bool) bool {
cfg := handler.GetCfg()
if !cfg.Behaviors.LimitFailedBinds {
return false
}
remoteAddr := l.getAddr(conn)
now := time.Now()
info, _ := l.sources[remoteAddr]
// if we have a failed bind...
if noteFailure {
info.failures <- failedBind{ts: time.Now()}
handler.GetLog().V(6).Info("CFRDBG: Failed Bind", "Count", len(info.failures))
// if we now have 3 failed binds in a row
if len(info.failures) == cfg.Behaviors.NumberOfFailedBinds {
handler.GetLog().V(6).Info("CFRDBG: 3 Failures")
// we cannot have more than 3 failed binds in our channel so pop the oldest one
pruned := <-info.failures
// if we have 3 failed bind in a row in less than 3 seconds
if pruned.ts.Add(cfg.Behaviors.PeriodOfFailedBinds * time.Second).After(now) {
handler.GetLog().V(6).Info("CFRDBG: 3 Rapid Failures")
// we will wait for 'n' seconds no matter what happens next
info.waitUntil = time.Now().Add(cfg.Behaviors.BlockFailedBindsFor * time.Second)
// purge our failure queue until we resume accepting operations
for len(info.failures) > 0 {
<-info.failures
}
}
}
}
// Prune old IPs
// TODO We should ensure that the time between prunings is bigger than the time to determine rapid failed binds
if l.nextPruning.Before(now) {
for sourceIP, sourceInfo := range l.sources {
if sourceInfo.lastSeen.Add(cfg.Behaviors.PruneSourcesOlderThan * time.Second).Before(now) {
delete(l.sources, sourceIP)
}
}
l.nextPruning = time.Now().Add(cfg.Behaviors.PruneSourceTableEvery * time.Second)
}
return false
}
func (l LDAPOpsHelper) getAddr(conn net.Conn) string {
fullAddr := conn.RemoteAddr().String()
sep := strings.LastIndex(fullAddr, ":")
if sep == -1 {
return fullAddr
}
return fullAddr[0:sep]
}
+102
View File
@@ -0,0 +1,102 @@
package handler
import (
"context"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Backend config.Backend
Handlers HandlerWrapper
Logger logr.Logger
Config *config.Config
Context *context.Context
YubiAuth *yubigo.YubiAuth
Helper Handler
LDAPHelper LDAPOpsHelper
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// newOptions initializes the available default options.
func NewOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Backend is our current backend
func Backend(val config.Backend) Option {
return func(o *Options) {
o.Backend = val
}
}
// Our friendly handlers for all backends
func Handlers(val HandlerWrapper) Option {
return func(o *Options) {
o.Handlers = val
}
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Context provides a function to set the context option.
func Context(val *context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// YubiAuth provides a function to set the yubiauth option.
func YubiAuth(val *yubigo.YubiAuth) Option {
return func(o *Options) {
o.YubiAuth = val
}
}
// If we specified a helper, for instance for OTP injection
func Helper(val Handler) Option {
return func(o *Options) {
o.Helper = val
}
}
// Global LDAP Handler
func LDAPHelper(val LDAPOpsHelper) Option {
return func(o *Options) {
o.LDAPHelper = val
}
}
+375
View File
@@ -0,0 +1,375 @@
package handler
import (
"context"
"crypto/tls"
"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/go-logr/logr"
"github.com/nmcclain/ldap"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
type ownCloudSession struct {
log logr.Logger
client *http.Client
user string
password string
endpoint string
useGraphAPI bool
}
type ownCloudHandler struct {
backend config.Backend
log logr.Logger
client *http.Client
sessions map[string]ownCloudSession
lock *sync.Mutex
}
// global lock for ownCloudHandler sessions & servers manipulation
var ownCloudLock sync.Mutex
func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.backend.BaseDN)
h.log.V(6).Info("Bind request", "binddn", bindDN, "basedn", h.backend.BaseDN, "src", conn.RemoteAddr())
stats.Frontend.Add("bind_reqs", 1)
// parse the bindDN - ensure that the bindDN ends with the BaseDN
if !strings.HasSuffix(bindDN, baseDN) {
h.log.V(2).Info("BindDN not part of our BaseDN", "binddn", bindDN, "basedn", h.backend.BaseDN)
return ldap.LDAPResultInvalidCredentials, nil
}
parts := strings.Split(strings.TrimSuffix(bindDN, baseDN), ",")
if len(parts) > 2 {
h.log.V(2).Info("BindDN should have only one or two parts", "binddn", bindDN, "numparts", len(parts))
return ldap.LDAPResultInvalidCredentials, nil
}
userName := strings.TrimPrefix(parts[0], "cn=")
// try to login
if !h.login(userName, bindSimplePw) {
h.log.V(2).Info("Login failed", "username", userName, "basedn", h.backend.BaseDN)
return ldap.LDAPResultInvalidCredentials, nil
}
// TODO reuse HTTP connection
id := connID(conn)
s := ownCloudSession{
log: h.log,
user: userName,
password: bindSimplePw,
endpoint: h.backend.Servers[0],
useGraphAPI: h.backend.UseGraphAPI,
client: h.client,
}
h.lock.Lock()
h.sessions[id] = s
h.lock.Unlock()
stats.Frontend.Add("bind_successes", 1)
h.log.V(6).Info("Bind success", "binddn", bindDN, "basedn", h.backend.BaseDN, "src", conn.RemoteAddr())
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.backend.BaseDN)
searchBaseDN := strings.ToLower(searchReq.BaseDN)
h.log.V(6).Info("Search request", "binddn", bindDN, "basedn", baseDN, "src", conn.RemoteAddr(), "filter", 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.backend.BaseDN)
}
if !strings.HasSuffix(searchBaseDN, h.backend.BaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: search BaseDN %s is not in our BaseDN %s", searchBaseDN, h.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.GIDNumber)}})
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.backend.GroupFormat, h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
case "posixaccount", "":
userName := ""
if searchBaseDN != strings.ToLower(h.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.V(6).Info("Could not get user", "username", userName, "err", err)
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("search error: error getting users")
}
for _, u := range users {
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.backend.NameFormat, *u.ID, h.backend.GroupFormat, "users", h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
}
stats.Frontend.Add("search_successes", 1)
h.log.V(6).Info("AP: Search OK", "filter", searchReq.Filter)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
// Add is not yet supported for the owncloud backend
func (h ownCloudHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Modify is not yet supported for the owncloud backend
func (h ownCloudHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Delete is not yet supported for the owncloud backend
func (h ownCloudHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// FindUser with the given username. Called by the ldap backend to authenticate the bind. Optional
func (h ownCloudHandler) FindUser(userName string, searchByUPN bool) (found bool, user config.User, err error) {
return false, config.User{}, nil
}
func (h ownCloudHandler) FindGroup(groupName string) (found bool, group config.Group, err error) {
return false, config.Group{}, 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 {
var req *http.Request
if h.backend.UseGraphAPI {
// TODO oc10 graphapi app should implement /me
req, _ = http.NewRequest("GET", h.backend.Servers[0]+"/users/"+name, nil)
} else {
// use provisioning api
meURL := fmt.Sprintf("%s/ocs/v2.php/cloud/user?format=json", h.backend.Servers[0])
req, _ = http.NewRequest("GET", meURL, nil)
}
req.SetBasicAuth(name, pw)
resp, err := h.client.Do(req)
if err != nil || resp.StatusCode != http.StatusOK {
h.log.Error(err, "failed login", "status", resp.StatusCode)
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) 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.endpoint)
req, _ := http.NewRequest("GET", groupsUrl, nil)
req.SetBasicAuth(s.user, s.password)
resp, err := s.client.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 := range f.Ocs.Data.Groups {
ret[i] = msgraph.Group{DirectoryObject: msgraph.DirectoryObject{Entity: msgraph.Entity{ID: &f.Ocs.Data.Groups[i]}}}
}
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 {
httpClient := &http.Client{
Transport: s,
}
g := msgraph.NewClient(httpClient)
g.SetURL(s.endpoint)
return g
}
func (s ownCloudSession) RoundTrip(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(s.user, s.password)
return s.client.Transport.RoundTrip(req)
}
func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
if s.useGraphAPI {
s.log.V(6).Info("using graph api")
ctx := context.Background()
req := s.NewClient().Users()
if len(userName) > 0 {
s.log.V(6).Info("fetching single user")
u, err := req.ID(userName).Request().Get(ctx)
if err != nil {
return nil, err
}
return []msgraph.User{*u}, nil
}
s.log.V(6).Info("fetching all users")
return req.Request().Get(ctx)
}
s.log.V(6).Info("using provisioning api")
usersUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/users?format=json", s.endpoint)
req, _ := http.NewRequest("GET", usersUrl, nil)
req.SetBasicAuth(s.user, s.password)
resp, err := s.client.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 := range f.Ocs.Data.Users {
ret[i] = msgraph.User{
DirectoryObject: msgraph.DirectoryObject{
Entity: msgraph.Entity{ID: &f.Ocs.Data.Users[i]},
},
}
}
return ret, nil
}
func (s ownCloudSession) redirectPolicyFunc(req *http.Request, via []*http.Request) error {
s.log.V(6).Info("Setting user and password", "username", s.user)
req.SetBasicAuth(s.user, s.password)
return nil
}
func NewOwnCloudHandler(opts ...Option) Handler {
options := newOptions(opts...)
return ownCloudHandler{
backend: options.Backend,
log: options.Logger,
sessions: make(map[string]ownCloudSession),
lock: &ownCloudLock,
client: &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: options.Backend.Insecure,
},
},
},
}
}
+25
View File
@@ -0,0 +1,25 @@
#####################
# Plugins Targets
#####################
# Note: to make a plugin compatible with a binary built in debug mode, add `-gcflags='all=-N -l'`
PLUGIN_OS ?= linux
PLUGIN_ARCH ?= amd64
plugins: plugin_mysql plugin_postgres plugin_sqlite
plugin_mysql: bin/mysql.so
bin/mysql.so: pkg/plugins/mysql.go pkg/plugins/basesqlhandler.go
GOOS=$(PLUGIN_OS) GOARCH=$(PLUGIN_ARCH) go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -buildmode=plugin -o $@ $^
plugin_postgres: bin/postgres.so
bin/postgres.so: pkg/plugins/postgres.go pkg/plugins/basesqlhandler.go
GOOS=$(PLUGIN_OS) GOARCH=$(PLUGIN_ARCH) go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -buildmode=plugin -o $@ $^
plugin_sqlite: bin/sqlite.so
bin/sqlite.so: pkg/plugins/sqlite.go pkg/plugins/basesqlhandler.go
GOOS=$(PLUGIN_OS) GOARCH=$(PLUGIN_ARCH) go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -buildmode=plugin -o $@ $^
+138
View File
@@ -0,0 +1,138 @@
# GLAuth Plugins
This folder contains plugins; that is, backends that are not compiled in GLAuth by default.
To quote 'Butonic' (Jörn Friedrich Dreyer):
> Just keep the 'lightweight' in mind.
To build either back-end, type
```
make plugin_name
```
where 'name' is the plugin's name; so, for instance: `make plugin_sqlite`
To build back-ends for specific architectures, specify `PLUGIN_OS` and `PLUGIN_ARCH` --
For instance, to build the sqlite plugin for the new Mac M1s:
```
make plugin_sqlite PLUGIN_OS=darwin PLUGIN_ARCH=arm64
```
## Database Plugins
To use a database plugin, edit the configuration file (see pkg/plugins/sample-database.cfg) so that:
```
...
[backend]
datastore = "plugin"
plugin = "dynamic library you created using the previous 'make' command"
database = "database connection string"
...
```
so, let's say you built the 'sqlite' plugin, you would now specify its library: `database = sqlite.so`
### SQLite, MySQL, Postgres
Tables:
- users, groups are self-explanatory
- includegroups store the 'includegroups' relationships
- othergroups, on the other hand, are a comma-separated list found in the users table (performance)
Here is how to insert example data using your database's REPL (more detailed information can be found in pkg/plugins/sample-database.cfg)
```sql
INSERT INTO groups(name, gidnumber) VALUES('superheros', 5501);
INSERT INTO groups(name, gidnumber) VALUES('svcaccts', 5502);
INSERT INTO groups(name, gidnumber) VALUES('civilians', 5503);
INSERT INTO groups(name, gidnumber) VALUES('caped', 5504);
INSERT INTO groups(name, gidnumber) VALUES('lovesailing', 5505);
INSERT INTO groups(name, gidnumber) VALUES('smoker', 5506);
INSERT INTO includegroups(parentgroupid, includegroupid) VALUES(5503, 5501);
INSERT INTO includegroups(parentgroupid, includegroupid) VALUES(5504, 5502);
INSERT INTO includegroups(parentgroupid, includegroupid) VALUES(5504, 5501);
INSERT INTO users(name, uidnumber, primarygroup, passsha256) VALUES('hackers', 5001, 5501, '6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a');
INSERT INTO users(name, uidnumber, primarygroup, passsha256) VALUES('johndoe', 5002, 5502, '6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a');
INSERT INTO users(name, mail, uidnumber, primarygroup, passsha256) VALUES('serviceuser', "serviceuser@example.com", 5003, 5502, '652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0');
INSERT INTO users(name, uidnumber, primarygroup, passsha256, othergroups) VALUES('user4', 5004, 5504, '652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0', '5505,5506');
```
This should be equivalent to this configuration:
```text
[[users]]
name = "hackers"
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users]]
name = "hackers"
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users]]
name = "johndoe"
uidnumber = 5002
primarygroup = 5502
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users]]
name = "serviceuser"
mail = "serviceuser@example.com"
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
[[users]]
name = "user4"
uidnumber = 5003
primarygroup = 5504
othergroups = [5505, 5506]
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
[[groups]]
name = "superheros"
gidnumber = 5501
[[groups]]
name = "svcaccts"
gidnumber = 5502
[[groups]]
name = "civilians"
gidnumber = 5503
includegroups = [ 5501 ]
[[groups]]
name = "caped"
gidnumber = 5504
includegroups = [ 5502, 5501 ]
```
and LDAP should return these `memberOf` values:
```text
uid: hackers
ou: superheros
memberOf: cn=caped,ou=groups,dc=militate,dc=com
memberOf: cn=civilians,ou=groups,dc=militate,dc=com
memberOf: cn=superheros,ou=groups,dc=militate,dc=com
uid: johndoe
ou: svcaccts
memberOf: cn=caped,ou=groups,dc=militate,dc=com
memberOf: cn=svcaccts,ou=groups,dc=militate,dc=com
uid: serviceuser
ou: caped
memberOf: cn=caped,ou=groups,dc=militate,dc=com
uid: user4
ou: caped
memberOf: cn=caped,ou=groups,dc=militate,dc=com
memberOf: cn=lovesailing,ou=groups,dc=militate,dc=com
memberOf: cn=smoker,ou=groups,dc=militate,dc=com
```
If you have the ldap client package installed, this can be easily confirmed by running
```
ldapsearch -H ldap://localhost:3893 -D cn=hackers,ou=superheros,dc=glauth,dc=com -w dogood -x -bdc=glauth,dc=com cn=hackers
```
and so on.
+500
View File
@@ -0,0 +1,500 @@
package main
import (
"database/sql"
"errors"
"fmt"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/handler"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
)
var configattributematcher = regexp.MustCompile(`(?i)\((?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)\)`)
type SqlBackend interface {
// Name used by database/sql when loading the driver
GetDriverName() string
// Create db/schema if necessary
CreateSchema(db *sql.DB)
//
GetPrepareSymbol() string
}
type database struct {
path string
cnx *sql.DB
}
type databaseHandler struct {
backend config.Backend
log logr.Logger
cfg *config.Config
yubikeyAuth *yubigo.YubiAuth
sqlBackend SqlBackend
database database
MemGroups []config.Group
ldohelper handler.LDAPOpsHelper
attmatcher *regexp.Regexp
}
// func NewDatabaseHandler_deprecated(log *logging.Logger, cfg *config.Config, yubikeyAuth *yubigo.YubiAuth, sqlBackend SqlBackend) handler.Handler {
func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
// Note: we will never terminate this connection pool.
db, err := sql.Open(sqlBackend.GetDriverName(), options.Backend.Database)
if err != nil {
options.Logger.Error(err, "Unable to open SQL database named '%s' error: %s", options.Backend.Database)
os.Exit(1)
}
err = db.Ping()
if err != nil {
options.Logger.Error(err, "Unable to communicate with SQL database error: %s", options.Backend.Database)
os.Exit(1)
}
dbInfo := database{
path: options.Backend.Database,
cnx: db,
}
handler := databaseHandler{
backend: options.Backend,
log: options.Logger,
cfg: options.Config,
yubikeyAuth: options.YubiAuth,
sqlBackend: sqlBackend,
database: dbInfo,
ldohelper: options.LDAPHelper,
attmatcher: configattributematcher}
sqlBackend.CreateSchema(db)
options.Logger.V(3).Info("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
return handler
}
func (h databaseHandler) GetBackend() config.Backend {
return h.backend
}
func (h databaseHandler) GetLog() logr.Logger {
return h.log
}
func (h databaseHandler) GetCfg() *config.Config {
return h.cfg
}
func (h databaseHandler) GetYubikeyAuth() *yubigo.YubiAuth {
return h.yubikeyAuth
}
func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
}
func (h databaseHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
return h.ldohelper.Search(h, bindDN, searchReq, conn)
}
// Add is not yet supported for the sql backend
func (h databaseHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Modify is not yet supported for the sql backend
func (h databaseHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
// Delete is not yet supported for the sql backend
func (h databaseHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error) {
var criterion string
if searchByUPN {
criterion = "lower(u.mail)"
} else {
criterion = "lower(u.name)"
}
user := config.User{}
found := false
err = h.database.cnx.QueryRow(fmt.Sprintf(`
SELECT u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey
FROM users u WHERE %s=%s`,
criterion,
h.sqlBackend.GetPrepareSymbol()), userName).Scan(
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
if err == nil {
found = true
}
return found, user, err
}
func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, err error) {
group := config.Group{}
found := false
err = h.database.cnx.QueryRow(fmt.Sprintf(`
SELECT g.gidnumber FROM groups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
&group.GIDNumber)
if err == nil {
found = true
}
return found, group, err
}
func (h databaseHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
h.MemGroups, err = h.memoizeGroups()
if err != nil {
return entries, err
}
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups,u.givenname,u.sn,u.mail,u.loginshell,u.homedirectory,u.disabled
FROM users u`)
if err != nil {
return entries, err
}
defer rows.Close()
var otherGroups string
var disabled int
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups, &u.GivenName, &u.SN, &u.Mail, &u.LoginShell, &u.Homedir, &disabled)
if err != nil {
return entries, err
}
u.OtherGroups = h.commaListToTable(otherGroups)
u.Disabled = h.intToBool(disabled)
entries = append(entries, h.getAccount(u))
}
return entries, nil
}
func (h databaseHandler) FindPosixGroups() (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
h.MemGroups, err = h.memoizeGroups()
if err != nil {
return entries, err
}
for _, g := range h.MemGroups {
entries = append(entries, h.getGroup(g))
}
return entries, nil
}
func (h databaseHandler) Close(boundDn string, conn net.Conn) error {
stats.Frontend.Add("closes", 1)
return nil
}
func (h databaseHandler) intToBool(value int) bool {
if value == 0 {
return false
}
return true
}
func (h databaseHandler) commaListToTable(commaList string) []int {
if len(commaList) == 0 {
return make([]int, 0)
}
rowsAsStrings := strings.Split(commaList, ",")
rowsAsInts := make([]int, len(rowsAsStrings))
for i, v := range rowsAsStrings {
iv, err := strconv.Atoi(v)
if err != nil {
return rowsAsInts
}
rowsAsInts[i] = iv
}
return rowsAsInts
}
func (h databaseHandler) memoizeGroups() ([]config.Group, error) {
workMemGroups := make([]*config.Group, 0)
rows, err := h.database.cnx.Query(`
SELECT g1.name,g1.gidnumber,ig.includegroupid
FROM groups g1
LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid
LEFT JOIN groups g2 ON ig.includegroupid=g2.gidnumber`)
if err != nil {
return nil, errors.New("Unable to memoize groups list")
}
defer rows.Close()
var groupName string
var groupId int
var includeId sql.NullInt64 // Store includeid from left join
var pg *config.Group
recentId := -1 // id of recently updated group
for rows.Next() {
err := rows.Scan(&groupName, &groupId, &includeId)
if err != nil {
return nil, errors.New("Unable to memoize groups list")
}
if recentId != groupId {
recentId = groupId
g := config.Group{Name: groupName, GIDNumber: groupId}
pg = &g // To manipulate end of slice
workMemGroups = append(workMemGroups, &g)
}
if includeId.Valid {
pg.IncludeGroups = append(pg.IncludeGroups, int(includeId.Int64))
}
}
memGroups := make([]config.Group, len(workMemGroups))
for i, v := range workMemGroups {
memGroups[i] = config.Group{Name: v.Name, GIDNumber: v.GIDNumber, IncludeGroups: v.IncludeGroups}
}
return memGroups, nil
}
// Used when looking up Posix Groups
func (h databaseHandler) getGroupMembers(gid int) []string {
members := make(map[string]bool)
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
FROM users u WHERE lower(u.name)=?`,
)
if err != nil {
// Silent fail... for now
return []string{}
}
defer rows.Close()
var otherGroups string
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
if err != nil {
return []string{}
}
if u.PrimaryGroup == gid {
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
} else {
u.OtherGroups = h.commaListToTable(otherGroups)
for _, othergid := range u.OtherGroups {
if othergid == gid {
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
members[dn] = true
}
}
}
}
for _, g := range h.MemGroups {
if gid == g.GIDNumber {
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
}
// Used exclusively when looking up Posix Groups
func (h databaseHandler) getGroupMemberIDs(gid int) []string {
members := make(map[string]bool)
rows, err := h.database.cnx.Query(`
SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
FROM users u`)
if err != nil {
// Silent fail... for now
return []string{}
}
defer rows.Close()
var otherGroups string
u := config.User{}
for rows.Next() {
err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
if err != nil {
return []string{}
}
if u.PrimaryGroup == gid {
members[u.Name] = true
} else {
u.OtherGroups = h.commaListToTable(otherGroups)
for _, othergid := range u.OtherGroups {
if othergid == gid {
members[u.Name] = true
}
}
}
}
for _, g := range h.MemGroups {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid {
h.log.V(3).Info(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
}
// Invoked for every user being returned from our database
func (h databaseHandler) getGroupDNs(gids []int) []string {
groups := make(map[string]bool)
for _, gid := range gids {
for _, g := range h.MemGroups {
if g.GIDNumber == gid {
dn := fmt.Sprintf("cn=%s,ou=groups,%s", g.Name, h.backend.BaseDN)
groups[dn] = true
}
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid && g.GIDNumber != gid {
includegroupdns := h.getGroupDNs([]int{g.GIDNumber})
for _, includegroupdn := range includegroupdns {
groups[includegroupdn] = true
}
}
}
}
}
g := []string{}
for k := range groups {
g = append(g, k)
}
sort.Strings(g)
return g
}
// Invoked for every user being returned from our database
func (h databaseHandler) getGroupName(gid int) string {
for _, g := range h.MemGroups {
if g.GIDNumber == gid {
return g.Name
}
}
return ""
}
// Toolbox
func (h databaseHandler) getGroup(g config.Group) *ldap.Entry {
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.GIDNumber)}})
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixGroup"}})
attrs = append(attrs, &ldap.EntryAttribute{"uniqueMember", h.getGroupMembers(g.GIDNumber)})
attrs = append(attrs, &ldap.EntryAttribute{"memberUid", h.getGroupMemberIDs(g.GIDNumber)})
dn := fmt.Sprintf("cn=%s,ou=groups,%s", g.Name, h.backend.BaseDN)
return &ldap.Entry{dn, attrs}
}
func (h databaseHandler) getAccount(u config.User) *ldap.Entry {
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.UIDNumber)}})
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{"userPrincipalName", []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(append(u.OtherGroups, u.PrimaryGroup))})
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.backend.BaseDN)
return &ldap.Entry{dn, attrs}
}
func main() {}
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/glauth/glauth/pkg/handler"
)
type MysqlBackend struct {
}
func NewMySQLHandler(opts ...handler.Option) handler.Handler {
backend := MysqlBackend{}
return NewDatabaseHandler(backend, opts...)
}
func (b MysqlBackend) GetDriverName() string {
return "mysql"
}
func (b MysqlBackend) GetPrepareSymbol() string {
return "?"
}
// Create db/schema if necessary
func (b MysqlBackend) CreateSchema(db *sql.DB) {
statement, _ := db.Prepare(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL,
uidnumber INTEGER NOT NULL,
primarygroup INTEGER NOT NULL,
othergroups VARCHAR(1024) DEFAULT '',
givenname VARCHAR(64) DEFAULT '',
sn VARCHAR(64) DEFAULT '',
mail VARCHAR(254) DEFAULT '',
loginshell VARCHAR(64) DEFAULT '',
homedirectory VARCHAR(64) DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 VARCHAR(64) DEFAULT '',
passbcrypt VARCHAR(64) DEFAULT '',
otpsecret VARCHAR(64) DEFAULT '',
yubikey VARCHAR(128) DEFAULT '')
`)
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX idx_user_name on users(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS groups (id INTEGER AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64) NOT NULL, gidnumber INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX idx_group_name on groups(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS includegroups (id INTEGER AUTO_INCREMENT PRIMARY KEY, parentgroupid INTEGER NOT NULL, includegroupid INTEGER NOT NULL)")
statement.Exec()
}
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"database/sql"
_ "github.com/lib/pq"
"github.com/glauth/glauth/pkg/handler"
)
type PostgresBackend struct {
}
func NewPostgresHandler(opts ...handler.Option) handler.Handler {
backend := PostgresBackend{}
return NewDatabaseHandler(backend, opts...)
}
func (b PostgresBackend) GetDriverName() string {
return "postgres"
}
func (b PostgresBackend) GetPrepareSymbol() string {
return "$1"
}
// Create db/schema if necessary
func (b PostgresBackend) CreateSchema(db *sql.DB) {
statement, _ := db.Prepare(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
uidnumber INTEGER NOT NULL,
primarygroup INTEGER NOT NULL,
othergroups TEXT DEFAULT '',
givenname TEXT DEFAULT '',
sn TEXT DEFAULT '',
mail TEXT DEFAULT '',
loginshell TEXT DEFAULT '',
homedirectory TEXT DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 TEXT DEFAULT '',
passbcrypt TEXT DEFAULT '',
otpsecret TEXT DEFAULT '',
yubikey TEXT DEFAULT '')
`)
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_user_name on users(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS groups (id SERIAL PRIMARY KEY, name TEXT NOT NULL, gidnumber INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_group_name on groups(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS includegroups (id SERIAL PRIMARY KEY, parentgroupid INTEGER NOT NULL, includegroupid INTEGER NOT NULL)")
statement.Exec()
}
+82
View File
@@ -0,0 +1,82 @@
#################
# glauth.conf
#################
# General configuration.
debug = true
# syslog = true
#
# Enable hot-reload of configuration on changes
# - does NOT work [ldap], [ldaps], [backend] or [api] sections
# watchconfig = true
#################
# yubikeyclientid = "yubi-api-clientid"
# 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 = false
listen = "0.0.0.0:3894"
cert = "certs/server.crt"
key = "certs/server.key"
#################
# The backend section controls the data store.
[backend]
datastore = "plugin"
# If "plugin," uncomment the line below
plugin = "bin/sqlite.so"
pluginhandler = "NewSQLiteHandler"
baseDN = "dc=glauth,dc=com"
# // MySQL //
# Uncomment this line to configure your MySQL database connection string:
# database = "glauth:glauth@tcp(127.0.0.1:3306)/glauth"
# Creating database:
# create database glauth identified;
# grant all on glauth.* to glauth@localhost identified by 'glauth';
# // Postgres //
# Uncomment this line to configure your Postgres database connection string:
# database = "host=127.0.0.1 port=5432 dbname=glauth user=glauth password=glauth sslmode=disable"
# Creating database:
# CREATE DATABASE <dbname>;
# CREATE USER <username> WITH PASSWORD '<userpassword>'
# GRANT ALL PRIVILEGES ON DATABASE <dbname> to <username>;
# // SQLite //
# Uncomment this line to configure your SQLite database connection string:
database = "gl.db"
## 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"
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = false # enable TLS for production!!
listen = "0.0.0.0:5555"
cert = "cert.pem"
key = "key.pem"
+45
View File
@@ -0,0 +1,45 @@
#################
# glauth.conf
#################
# General configuration.
#debug = true
syslog = true
#################
# Server configuration.
[ldap]
enabled = true
listen = "0.0.0.0:389"
[ldaps]
enabled = true
listen = "0.0.0.0:636"
cert = "glauth.crt"
key = "glauth.key"
#################
# The backend section controls the data store.
[backend]
datastore = "ldap"
servers = [ "ldaps://server1:636", "ldaps://server2:636" ]
# Ignore SSL certificate errors when connecting to backend LDAP servers.
#insecure = true
[helper]
enabled = true
datastore = "plugin"
plugin = "bin/sqlite.so"
pluginhandler = "NewSQLiteHandler"
database = "glh.db"
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = true
listen = "localhost:5555"
cert = "glauth.crt"
key = "glauth.key"
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
"github.com/glauth/glauth/pkg/handler"
)
type SqliteBackend struct {
}
func NewSQLiteHandler(opts ...handler.Option) handler.Handler {
backend := SqliteBackend{}
return NewDatabaseHandler(backend, opts...)
}
func (b SqliteBackend) GetDriverName() string {
return "sqlite3"
}
func (b SqliteBackend) GetPrepareSymbol() string {
return "?"
}
// Create db/schema if necessary
func (b SqliteBackend) CreateSchema(db *sql.DB) {
statement, _ := db.Prepare(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
uidnumber INTEGER NOT NULL,
primarygroup INTEGER NOT NULL,
othergroups TEXT DEFAULT '',
givenname TEXT DEFAULT '',
sn TEXT DEFAULT '',
mail TEXT DEFAULT '',
loginshell TYEXT DEFAULT '',
homedirectory TEXT DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 TEXT DEFAULT '',
passbcrypt TEXT DEFAULT '',
otpsecret TEXT DEFAULT '',
yubikey TEXT DEFAULT '')
`)
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_user_name on users(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS groups (id INTEGER PRIMARY KEY, name TEXT NOT NULL, gidnumber INTEGER NOT NULL)")
statement.Exec()
statement, _ = db.Prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_group_name on groups(name)")
statement.Exec()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS includegroups (id INTEGER PRIMARY KEY, parentgroupid INTEGER NOT NULL, includegroupid INTEGER NOT NULL)")
statement.Exec()
}
+50
View File
@@ -0,0 +1,50 @@
package server
import (
"context"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger logr.Logger
Config *config.Config
Context context.Context
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
+173
View File
@@ -0,0 +1,173 @@
package server
import (
"errors"
"fmt"
"plugin"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/handler"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
)
type LdapSvc struct {
log logr.Logger
c *config.Config
yubiAuth *yubigo.YubiAuth
l *ldap.Server
}
func NewServer(opts ...Option) (*LdapSvc, error) {
options := newOptions(opts...)
s := LdapSvc{
log: options.Logger,
c: options.Config,
}
var err error
if len(s.c.YubikeyClientID) > 0 && len(s.c.YubikeySecret) > 0 {
s.yubiAuth, err = yubigo.NewYubiAuth(s.c.YubikeyClientID, s.c.YubikeySecret)
if err != nil {
return nil, errors.New("Yubikey Auth failed")
}
}
var helper handler.Handler
loh := handler.NewLDAPOpsHelper()
// instantiate the helper, if any
if s.c.Helper.Enabled {
switch s.c.Helper.Datastore {
case "config":
helper = handler.NewConfigHandler(
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
)
case "plugin":
plug, err := plugin.Open(s.c.Helper.Plugin)
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to load specified helper plugin: %s", err))
}
nph, err := plug.Lookup(s.c.Helper.PluginHandler)
if err != nil {
return nil, errors.New("Unable to find 'NewPluginHandler' in loaded helper plugin")
}
initFunc, ok := nph.(func(...handler.Option) handler.Handler)
if !ok {
return nil, errors.New("Loaded helper plugin lacks a proper NewPluginHandler function")
}
// Normally, here, we would somehow have imported our plugin into our
// handler namespace. Oops?
helper = initFunc(
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
)
default:
return nil, fmt.Errorf("unsupported helper %s - must be one of 'config', 'plugin'", s.c.Helper.Datastore)
}
s.log.V(3).Info("Using helper", "datastore", s.c.Helper.Datastore)
}
backendCounter := -1
allHandlers := handler.HandlerWrapper{Handlers: make([]handler.Handler, 10), Count: &backendCounter}
// configure the backends
s.l = ldap.NewServer()
s.l.EnforceLDAP = true
for i, backend := range s.c.Backends {
var h handler.Handler
switch backend.Datastore {
case "ldap":
h = handler.NewLdapHandler(
handler.Backend(backend),
handler.Handlers(allHandlers),
handler.Logger(s.log),
handler.Helper(helper),
)
case "owncloud":
h = handler.NewOwnCloudHandler(
handler.Backend(backend),
handler.Logger(s.log),
)
case "config":
h = handler.NewConfigHandler(
handler.Backend(backend),
handler.Logger(s.log),
handler.Config(s.c), // TODO only used to access Users and Groups, move that to dedicated options
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
)
case "plugin":
plug, err := plugin.Open(backend.Plugin)
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to load specified backend plugin: %s", err))
}
nph, err := plug.Lookup(backend.PluginHandler)
if err != nil {
return nil, errors.New("Unable to find 'NewPluginHandler' in loaded backend plugin")
}
initFunc, ok := nph.(func(...handler.Option) handler.Handler)
if !ok {
return nil, errors.New("Loaded backend plugin lacks a proper NewPluginHandler function")
}
// Normally, here, we would somehow have imported our plugin into our
// handler namespace. Oops?
h = initFunc(
handler.Backend(backend),
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
)
default:
return nil, fmt.Errorf("unsupported backend %s - must be one of 'config', 'ldap','owncloud' or 'plugin'", backend.Datastore)
}
s.log.V(3).Info("Loading backend", "datastore", backend.Datastore, "position", i)
// Only our first backend will answer proper LDAP queries.
// Note that this could evolve towars something nicer where we would maintain
// multiple binders in addition to the existing multiple LDAP backends
if i == 0 {
s.l.BindFunc("", h)
s.l.SearchFunc("", h)
s.l.CloseFunc("", h)
}
allHandlers.Handlers[i] = h
backendCounter++
}
return &s, nil
}
// ListenAndServe listens on the TCP network address s.c.LDAP.Listen
func (s *LdapSvc) ListenAndServe() error {
s.log.V(3).Info("LDAP server listening", "address", s.c.LDAP.Listen)
return s.l.ListenAndServe(s.c.LDAP.Listen)
}
// ListenAndServeTLS listens on the TCP network address s.c.LDAPS.Listen
func (s *LdapSvc) ListenAndServeTLS() error {
s.log.V(3).Info("LDAPS server listening", "address", s.c.LDAPS.Listen)
return s.l.ListenAndServeTLS(
s.c.LDAPS.Listen,
s.c.LDAPS.Cert,
s.c.LDAPS.Key,
)
}
// Shutdown ends listeners by sending true to the ldap serves quit channel
func (s *LdapSvc) Shutdown() {
s.l.Quit <- true
}
+10
View File
@@ -0,0 +1,10 @@
package stats
import "expvar"
// exposed expvar variables
var (
Frontend = expvar.NewMap("proxy_frontend")
Backend = expvar.NewMap("proxy_backend")
General = expvar.NewMap("proxy")
)
+5
View File
@@ -0,0 +1,5 @@
package stats
type Stringer string
func (s Stringer) String() string { return "\"" + string(s) + "\"" }
+42
View File
@@ -0,0 +1,42 @@
#################
# glauth.conf
#################
# General configuration.
#debug = true
syslog = true
#################
# Server configuration.
[ldap]
enabled = true
listen = "0.0.0.0:3893"
[ldaps]
enabled = false
listen = "0.0.0.0:636"
cert = "glauth.crt"
key = "glauth.key"
#################
# The backend section controls the data store.
[[backends]]
datastore = "ldap"
servers = [ "ldap://localhost:389" ]
[[backends]]
datastore = "config"
[[users]]
name = "otpuser"
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
#################
# Enable and configure the optional REST API here.
[api]
enabled = false
tls = true
listen = "localhost:5555"
cert = "glauth.crt"
key = "glauth.key"
+11 -7
View File
@@ -7,12 +7,16 @@
syslog = true
#################
# The frontend section controls how clients connect to the proxy.
[frontend]
tls = true
# Server configuration.
[ldap]
enabled = true
listen = "0.0.0.0:389"
[ldaps]
enabled = true
listen = "0.0.0.0:636"
cert = "cert.pem"
key = "key.pem"
cert = "glauth.crt"
key = "glauth.key"
#################
# The backend section controls the data store.
@@ -29,6 +33,6 @@ syslog = true
enabled = true
tls = true
listen = "localhost:5555"
cert = "cert.pem"
key = "key.pem"
cert = "glauth.crt"
key = "glauth.key"
+25
View File
@@ -0,0 +1,25 @@
#################
# 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"
UseGraphAPI = true
servers = ["https://cloud.example.com/index.php/apps/graphapi/v1.0/"]
# if not using the graph API only provide the url to the owncloud instance
#UseGraphAPI = false
#servers = ["https://other.example.com/owncloud/"]
+71 -15
View File
@@ -4,29 +4,66 @@
#################
# General configuration.
debug = true
#syslog = true
# syslog = true
#
# Enable hot-reload of configuration on changes
# - does NOT work [ldap], [ldaps], [backend] or [api] sections
# watchconfig = true
#################
# yubikeyclientid = "yubi-api-clientid"
# yubikeysecret = "yubi-api-secret"
#################
# 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"
# Server configuration.
[ldap]
enabled = true
# run on a non privileged port
listen = "0.0.0.0:3893"
[ldaps]
# to enable ldaps genrerate a certificate, eg. with:
# openssl req -x509 -newkey rsa:4096 -keyout glauth.key -out glauth.crt -days 365 -nodes -subj '/CN=`hostname`'
enabled = false
listen = "0.0.0.0:3894"
cert = "glauth.crt"
key = "glauth.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"
[behaviors]
# Enable a "fail2ban" type backoff mechanism temporarily banning repeated failed login attempts
LimitFailedBinds = true
# How many failed login attempts are allowed before a ban is imposed
NumberOfFailedBinds = 3
# How long (in seconds) is the window for failed login attempts
PeriodOfFailedBinds = 10
# How long (in seconds) is the ban duration
BlockFailedBindsFor = 60
# Clean learnt IP addresses every N seconds
PruneSourceTableEvery = 600
# Clean learnt IP addresses not seen in N seconds
PruneSourcesOlderThan = 600
#################
# The users section contains a hardcoded list of valid users.
# to create a passSHA256: echo -n "mysecret" | openssl dgst -sha256
[[users]]
name = "hackers"
unixid = 5001
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
@@ -36,41 +73,60 @@ debug = true
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
unixid = 5002
uidnumber = 5002
primarygroup = 5501
loginShell = "/bin/sh"
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"
unixid = 5003
mail = "serviceuser@example.com"
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
# Test user showing 2 factor auth authentication
[[users]]
name = "otpuser"
unixid = 5004
uidnumber = 5004
primarygroup = 5501
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
yubikey = "vvjrcfalhlaa"
[[users]]
name = "uberhackers"
uidnumber = 5005
primarygroup = 5501
# bcrypt format: hex($2y$2^<number of rounds>$<salt>$<hash>)
passappbcrypt = [
"243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
]
# uncomment and comment out above array to test password with otp code
# passappbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
#################
# The groups section contains a hardcoded list of valid users.
[[groups]]
name = "superheros"
unixid = 5501
gidnumber = 5501
[[groups]]
name = "svcaccts"
unixid = 5502
gidnumber = 5502
[[groups]]
name = "vpn"
unixid = 5503
gidnumber = 5503
includegroups = [ 5501 ]
#################
@@ -78,6 +134,6 @@ debug = true
[api]
enabled = true
tls = false # enable TLS for production!!
listen = "localhost:5555"
listen = "0.0.0.0:5555"
cert = "cert.pem"
key = "key.pem"
+12 -9
View File
@@ -7,10 +7,13 @@ debug = true
#syslog = true
#################
# The frontend section controls how clients connect to the proxy.
[frontend]
tls = false # enable TLS for production!!
listen = "localhost:3893"
[ldap]
enabled = true
listen = "localhost:389"
[ldaps]
enabled = false
listen = "0.0.0.0:636"
cert = "cert.pem"
key = "key.pem"
@@ -25,7 +28,7 @@ debug = true
# to create a passSHA256: echo -n "mysecret" | openssl dgst -sha256
[[users]]
name = "hackers"
unixid = 5001
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
@@ -35,7 +38,7 @@ debug = true
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
unixid = 5002
uidnumber = 5002
primarygroup = 5501
loginShell = "/bin/sh"
homeDir = "/root"
@@ -45,7 +48,7 @@ debug = true
[[users]]
name = "serviceuser"
unixid = 5003
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
@@ -53,11 +56,11 @@ debug = true
# The groups section contains a hardcoded list of valid users.
[[groups]]
name = "superheros"
unixid = 5501
gidnumber = 5501
[[groups]]
name = "svcaccts"
unixid = 5502
gidnumber = 5502
#################
# Enable and configure the optional REST API here.
+16 -1
View File
@@ -2,12 +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."
+113 -10
View File
@@ -5,14 +5,22 @@ ou: superheros
uidNumber: 5001
accountStatus: active
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/bash
homeDirectory: /home/hackers
description: hackers via LDAP
gecos: hackers via LDAP
description: hackers
gecos: hackers
gidNumber: 5501
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
dn: cn=johndoe,ou=superheros,dc=glauth,dc=com
cn: johndoe
@@ -23,15 +31,24 @@ ou: superheros
uidNumber: 5002
accountStatus: active
mail: jdoe@example.com
userPrincipalName: jdoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
homeDirectory: /root
description: johndoe via LDAP
gecos: johndoe via LDAP
description: johndoe
gecos: johndoe
gidNumber: 5501
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
w3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4h
C/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLV
@@ -48,17 +65,26 @@ ou: superheros
uidNumber: 5003
accountStatus: active
mail: jdoe2@example.com
userPrincipalName: jdoe2@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
homeDirectory: /home/jamesdoe
description: jamesdoe via LDAP
gecos: jamesdoe via LDAP
description: jamesdoe
gecos: jamesdoe
gidNumber: 5501
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
w3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4h
C/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLV
@@ -75,17 +101,86 @@ ou: superheros
uidNumber: 5004
accountStatus: active
mail: adoe@example.com
userPrincipalName: adoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
homeDirectory: /home/alexdoe
description: alexdoe via LDAP
gecos: alexdoe via LDAP
description: alexdoe
gecos: alexdoe
gidNumber: 5501
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
dn: cn=jackdoe,ou=superheros,dc=glauth,dc=com
cn: jackdoe
uid: jackdoe
givenName: Jack
sn: Doe
ou: superheros
uidNumber: 5005
accountStatus: active
mail: jdoe3@example.com
userPrincipalName: 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
userPrincipalName: 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
@@ -94,11 +189,19 @@ ou: svcaccts
uidNumber: 5003
accountStatus: active
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/bash
homeDirectory: /home/serviceuser
description: serviceuser via LDAP
gecos: serviceuser via LDAP
description: serviceuser
gecos: serviceuser
gidNumber: 5502
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=svcaccts,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
+34 -7
View File
@@ -1,20 +1,26 @@
dn: cn=superheros,ou=groups,dc=glauth,dc=com
cn: superheros
description: superheros via LDAP
uid: superheros
description: superheros
gidNumber: 5501
objectClass: posixGroup
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
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
description: svcaccts via LDAP
uid: svcaccts
description: svcaccts
gidNumber: 5502
objectClass: posixGroup
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
@@ -22,57 +28,78 @@ memberUid: serviceuser
dn: cn=vpnaccess,ou=groups,dc=glauth,dc=com
cn: vpnaccess
description: vpnaccess via LDAP
uid: vpnaccess
description: vpnaccess
gidNumber: 5503
objectClass: posixGroup
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
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
description: allaccs via LDAP
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
description: mailadmin via LDAP
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
description: webmail via LDAP
uid: webmail
description: webmail
gidNumber: 5506
objectClass: posixGroup
dn: cn=fulltime,ou=groups,dc=glauth,dc=com
cn: fulltime
description: fulltime via LDAP
uid: fulltime
description: fulltime
gidNumber: 5507
objectClass: posixGroup
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
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
+10 -2
View File
@@ -5,12 +5,20 @@ ou: superheros
uidNumber: 5001
accountStatus: active
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/bash
homeDirectory: /home/hackers
description: hackers via LDAP
gecos: hackers via LDAP
description: hackers
gecos: hackers
gidNumber: 5501
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
+11 -2
View File
@@ -7,15 +7,24 @@ ou: superheros
uidNumber: 5002
accountStatus: active
mail: jdoe@example.com
userPrincipalName: jdoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
homeDirectory: /root
description: johndoe via LDAP
gecos: johndoe via LDAP
description: johndoe
gecos: johndoe
gidNumber: 5501
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
w3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4h
C/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLV
+10 -2
View File
@@ -5,11 +5,19 @@ ou: svcaccts
uidNumber: 5003
accountStatus: active
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/bash
homeDirectory: /home/serviceuser
description: serviceuser via LDAP
gecos: serviceuser via LDAP
description: serviceuser
gecos: serviceuser
gidNumber: 5502
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=svcaccts,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
+11 -2
View File
@@ -7,17 +7,26 @@ ou: superheros
uidNumber: 5003
accountStatus: active
mail: jdoe2@example.com
userPrincipalName: jdoe2@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
homeDirectory: /home/jamesdoe
description: jamesdoe via LDAP
gecos: jamesdoe via LDAP
description: jamesdoe
gecos: jamesdoe
gidNumber: 5501
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwV
w3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4h
C/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLV
+11 -2
View File
@@ -7,15 +7,24 @@ ou: superheros
uidNumber: 5004
accountStatus: active
mail: adoe@example.com
userPrincipalName: adoe@example.com
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/sh
homeDirectory: /home/alexdoe
description: alexdoe via LDAP
gecos: alexdoe via LDAP
description: alexdoe
gecos: alexdoe
gidNumber: 5501
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
shadowExpire: -1
shadowFlag: 134538308
shadowInactive: -1
shadowLastChange: 11000
shadowMax: 99999
shadowMin: -1
shadowWarning: 7
+67 -2
View File
@@ -4,6 +4,15 @@ export CLEANUP="$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)"
@@ -25,6 +34,9 @@ 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 glauth at PID=$glauthPid"
@@ -60,9 +72,9 @@ function bindTest() {
exitCode="$?"
if [[ "$exitCode" = "0" ]] ; then
echo " - PASS : 2FA test '$4'";
echo " - PASS : Bind test '$4'";
else
echo " - FAIL : 2FA test '$4'";
echo " - FAIL : Bind test '$4'";
FAIL="1"
fi
@@ -170,6 +182,59 @@ bindTest "cn=alexdoe,ou=superheros,dc=glauth,dc=com" \
"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
+52 -12
View File
@@ -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"
@@ -25,7 +28,7 @@ debug = true
# to create a passSHA256: echo -n "mysecret" | openssl dgst -sha256
[[users]]
name = "hackers"
unixid = 5001
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
@@ -35,7 +38,7 @@ debug = true
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
unixid = 5002
uidnumber = 5002
primarygroup = 5501
loginShell = "/bin/sh"
homeDir = "/root"
@@ -48,7 +51,7 @@ debug = true
givenname="James"
sn="Doe"
mail = "jdoe2@example.com"
unixid = 5003
uidnumber = 5003
primarygroup = 5501
loginShell = "/bin/sh"
otherGroups = [5505,5507]
@@ -62,7 +65,7 @@ debug = true
givenname="Alex"
sn="Doe"
mail = "adoe@example.com"
unixid = 5004
uidnumber = 5004
primarygroup = 5501
loginShell = "/bin/sh"
otherGroups = [5505,5507]
@@ -70,9 +73,46 @@ debug = true
passsha256 = "b1cae08a1fd8b0bd0855e48f74ba2f8548904dd5de72311e6234617a3f661e16" # ThisAloneWontWork!
# App PW test user without OTP
[[users]]
name = "jackdoe"
givenname="Jack"
sn="Doe"
mail = "jdoe3@example.com"
uidnumber = 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"
uidnumber = 5006
primarygroup = 5501
loginShell = "/bin/sh"
otherGroups = [5505,5507]
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
passsha256 = "ff522a502031e5f37054892d642c2c0f36af6c4e8f62225470d4a1a99f3c9c2a" #dogood1
passappsha256 = [
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
]
[[users]]
name = "serviceuser"
unixid = 5003
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
@@ -80,35 +120,35 @@ debug = true
# The groups section contains a hardcoded list of valid users.
[[groups]]
name = "superheros"
unixid = 5501
gidnumber = 5501
[[groups]]
name = "svcaccts"
unixid = 5502
gidnumber = 5502
[[groups]]
name = "vpnaccess"
unixid = 5503
gidnumber = 5503
includegroups = [ 5501 ]
[[groups]]
name = "allaccs"
unixid = 5504
gidnumber = 5504
includegroups = [ 5501, 5502 ]
[[groups]]
name = "mailadmin"
unixid = 5505
gidnumber = 5505
[[groups]]
name = "webmail"
unixid = 5506
gidnumber = 5506
[[groups]]
name = "fulltime"
unixid = 5507
gidnumber = 5507
#################
# Enable and configure the optional REST API here.
-62
View File
@@ -1,62 +0,0 @@
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"strings"
)
// RunAPI provides a basic REST API
func RunAPI(cfg *config) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Debug(fmt.Sprintf("Web: %s", r.URL.Path))
if r.URL.Path != "/" {
log.Debug("Web 404")
http.NotFound(w, r)
return
}
pageTemplate, err := 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)
if cfg.API.TLS {
log.Notice(fmt.Sprintf("Starting HTTPS server on %s", cfg.API.Listen))
err := http.ListenAndServeTLS(cfg.API.Listen, cfg.API.Cert, cfg.API.Key, nil)
if err != nil {
log.Fatal(fmt.Sprintf("Error starting HTTPS server: %s", err.Error()))
}
} else {
log.Notice(fmt.Sprintf("Starting HTTP server on %s", cfg.API.Listen))
err := http.ListenAndServe(cfg.API.Listen, nil)
if err != nil {
log.Fatal(fmt.Sprintf("Error starting HTTP server: %s", err.Error()))
}
}
}
// 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
}
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) }