Compare commits

...

140 Commits

Author SHA1 Message Date
Chris F Ravenscroft aa0414f02e chore(dev): release 2.3.0 (#337) 2023-10-01 22:51:37 -07:00
Chris F Ravenscroft 349431c6ca feat: Update migration code to support table names (#339) 2023-10-01 22:04:41 -07:00
Chris F Ravenscroft 15ed058754 chore: update docker sqlite with ldapgroups (#338) 2023-10-01 21:37:16 -07:00
Billy Olsen 675b236328 Fix(plugins)!: Rename the groups table to ldapgroups (#326)
The groups table name is a reserved keyword in mysql and requires that
the table name be escaped with backticks in order to use the groups
name. Other databases such as postgres do not support the use of the
backticks in the query statements. This change renames the groups table
to ldapgroups in oder to avoid this problem altogether.

This does have a dependency on the backend sql plugins to alter the
schema to match the new name.

Signed-off-by: Billy Olsen <billy.olsen@gmail.com>
Co-authored-by: Chris F Ravenscroft <chris@voilaweb.com>
2023-10-01 21:28:49 -07:00
Chris F Ravenscroft 75d02ee368 release-please 2023-10-02 00:16:50 +00:00
Chris F Ravenscroft 0c11325a24 chore: release 2.3.0
Release-As: 2.3.0
2023-10-02 00:02:01 +00:00
Chris F Ravenscroft 0c7a513852 Resolved minor conflict 2023-09-30 22:22:50 +00:00
Chris F Ravenscroft d8398285f1 Ensure same go version for main and plugins 2023-09-30 22:12:24 +00:00
shipperizer 27c9d4cb42 build: reuse build target for each platform specific compilation 2023-09-28 10:13:37 +01:00
shipperizer 3114106563 build: create mkbindir, build and sha256 targets 2023-09-28 10:13:37 +01:00
shipperizer 75abe08313 build: use . for BUILD_FILES var to allow buildvcs flag 2023-09-28 10:13:37 +01:00
shipperizer 4e62b7cd89 build: make build vars optional and passable via env 2023-09-28 10:13:37 +01:00
Chris F Ravenscroft 08a89a9861 Sum types, preparation (#330) 2023-09-09 13:35:42 -07:00
Daniel Barney a4450d4558 Allow custom S3 endpoint to be used (#329)
Co-authored-by: Daniel Barney <daniel@thoughtplot.io>
2023-09-09 13:31:40 -07:00
Chris F Ravenscroft 0b7910c03c users is an organizational unit (#323) 2023-09-09 13:09:52 -07:00
Billy Olsen 944d602077 Update the TRIM_FLAGS to use -trimpath (#327)
The current TRIM_FLAGS specify gcflags and asmflags directly in order to
trim the path of the resulting binaries. This results in an error where
the plugins cannot be loaded due to the error: "built with a different
version of package". Per https://github.com/golang/go/issues/51955 in the
golang repository, this is explicitly not supported. Switching to use the
go build command's -trimpath flag resolves the issue.

Signed-off-by: Billy Olsen <billy.olsen@gmail.com>
2023-09-03 19:15:25 -07:00
guangwu 596d28efbd chore: remove refs to deprecated io/ioutil (#322)
Thanks!
2023-07-22 13:02:24 -07:00
Chris F Ravenscroft 7f925565a6 Merge branch 'candlerb-candlerb/rdn' into dev 2023-07-03 22:43:22 +00:00
Chris F Ravenscroft f5ea3d93bf Merge branch 'candlerb/rdn' of github.com:candlerb/glauth into candlerb-candlerb/rdn 2023-07-03 22:37:56 +00:00
Chris F Ravenscroft 34a6b30b8f Feature: introduce legacy behavior, based on past versions (#318) 2023-07-03 15:33:01 -07:00
Chris F Ravenscroft 9bbad511cb Merge branch 'candlerb-candlerb/uniquemember' into dev 2023-07-03 14:59:17 -07:00
Nick Borgers 7cef07931c Update go.work to 1.19 (#315) 2023-05-28 13:18:00 -07:00
Konstantin Shalygin 37fa74892e Added anonymousdse backend option to config sample (#316)
Fixes #83
2023-05-28 13:12:07 -07:00
Nick Borgers efbd64504d add help command to Makefile (#307)
* add fanciness to Makefile
2023-04-22 13:36:40 -07:00
Nick Borgers 1920c4324f Add devcontainer (#306)
* add option for devcontainer
2023-04-22 13:31:35 -07:00
Chris F Ravenscroft 7e7a5f59d1 Update Go Versions to 1.19, 1.20 (#314)
* Update Go Versions to 1.19, 1.20
2023-04-22 13:28:21 -07:00
NW 83f4305e36 searchMaybePosixAccounts: add filter to respect searchBaseDN (#304)
* searchMaybePosixAccounts: add filter to respect searchBaseDN
2023-04-22 13:09:05 -07:00
dependabot[bot] b45259d567 Bump golang.org/x/crypto from 0.0.0-20210711020723-a769d52b0f97 to 0.1.0 in /v2 (#303)
* Bump golang.org/x/crypto in /v2

Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20210711020723-a769d52b0f97 to 0.1.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/commits/v0.1.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-22 13:01:02 -07:00
lgtm-com[bot] 3f9f67375f Add CodeQL workflow for GitHub code scanning (#295) 2023-04-22 12:56:32 -07:00
Sven Wick cb761e8e70 Update sample-simple.cfg (#275)
Fix typo
2023-04-22 12:44:03 -07:00
securitytime 7c3314a504 Update README.md (#266)
Fixed a broken link

Co-authored-by: Chris F Ravenscroft <chris@voilaweb.com>
2023-04-22 12:38:28 -07:00
Brian Candler 690917e4e0 Use NameFormat/GroupFormat when constructing RDN attributes
Before:

dn: ou=vpn,ou=users,dc=glauth,dc=com
cn: vpn

After:

dn: ou=vpn,ou=users,dc=glauth,dc=com
ou: vpn    <<< THIS HAS CHANGED

NOTE: whereas previously a search filter (cn=vpn) would have found the
group, now you need (ou=vpn)
2023-04-12 16:23:55 +01:00
Brian Candler 0ccf4926ff Construct DNs under ou=users for uniqueMember group attribute
Before:

dn: ou=vpn,ou=users,dc=glauth,dc=com
...
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com

After:

dn: ou=vpn,ou=users,dc=glauth,dc=com
...
uniqueMember: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com
                                       ^^^^^^^^
2023-04-12 16:01:07 +01:00
Chris F Ravenscroft 598a688214 CodeSee integration 2022-12-24 13:42:20 -08:00
Chris F Ravenscroft 0e7769ff84 No SysLog on Windows (#289) (#290) 2022-11-12 14:59:43 -08:00
Chris F Ravenscroft 6500bbaf36 No SysLog on Windows (#289) 2022-11-12 14:55:55 -08:00
Chris F Ravenscroft e92197ac08 Resync dev to master 2022-11-06 20:38:20 -08:00
Chris F Ravenscroft 65385fb82d Resync dev without old crud 2022-11-06 20:34:11 -08:00
Chris F Ravenscroft aadf150fec Dev -> Master get rid of old Docker files (#288)
* Reverse pull request from master to dev.

Reverse pull request.

* Fix/docker latest tag (#260)

* Dev (#254)

* use functional options pattern to inject logr (#124)

* use functional options pattern to inject logr

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

* cleanup log formatting

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

* allow clean shutdown (#126)

* fix owncloud posix query, log message and provisioning api results (#128)

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

* don't hardcode graphapi endpoint

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

* add support for write handlers (#135)

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

* config: match shadowaccount objectlcass (#136)

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

* Makefile compatiblility (#134)

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update travis.yml (#154)

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

* check owncloud status code is ok (#153)

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

* Bulid and push multiarch docker images (#142)

Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't mix graph and provisioning api (#157)

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

* Reuse http client as much as possible and allow insecure transport (#160)

* Allow using configmaps when deploying in kubernetes (#161)

* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals

* Fix API server json formating (#163) (#164)

* Update README.md (#167)

Fixing bad port in quickstart

* Use port 3893 with ldapsearch in example (#150)

* feat: add flags for ldap listen addresses (#169)

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Assets build fix. (#171)

* Docker build fix.

* Makefile fix.

* update readme, config and deps (#170)

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

* Remove byanke's donation fields

* Database plugins (#133)

This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying

* Mac M1 Support and LDAP Req Attributes (#192)

* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas

* Feature/upgrade ldap library version (#194)

* Stronger, salted paswords using bcrypt. (#195)

* Fixed badges in README file and added a couple improvements (#196)

* fix lock for ownCloud / graph backend (#198)

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

* the config struct is only needed by the config backend (#199)

* Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201)

* fix: keep watch config file when changed,renamed,removed (#189)

I will need to run `glauth.go` through `gofmt` myself but that is fine.

* Returning when unable to start config watcher (#203)

* Introducing goconvey testing and refactoring of config and ... (#204)

* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes

* Allow bind operations with no group provided (#205)

* Support for userPrincipalName binding and browsing. (#206)

* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users

* Rate limit after failed binds (#207)

* Capabilities -- part 2 (#214) + do not dump config at startup

* Attempting migration from Travis to Github Actions

* Migration Step#2

* Migration Step#3

* Migration Step#4

* Migration Step#5

* Migration Step#6

* Migration Step#7

* Migration Step#8

* Migration Step#9

* Migration Step#9

* Migration dev...

* Migration dev...

* Migration dev...

* Migration dev...

* Docker with plugins, first step (#215)

* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI

* - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions

* Plugins now run on distroless (#217)

* Getting rid of Travis CI now that it doesn't support FOSS anymore.

* Internal Stats -- performance view (#221)

* Local merge

* Update CI to forget about bindata

* Forgot to cleanup the cleanup...

* assets: use stdlib "embed" package (#200)

This should make it easier to include GLAuth in other projects using `go get`

* 'Airgapping' web assets for security and preventing breakage (#227)

* v2 -- V2 hierarchy (#228)

* Search refactoring, tree traversal and scope correctness (#229)

Search refactoring, tree traversal and scope correctness.

* Feature/multi cfg (#233)

* Feature/custom attributes (#240)

* Makefiles: platform releases (#241)

* Feature/release script (#242)

* Bug/fix docker build in v2 (#244)

* Fix #246 and #252

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* Cleanup

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* Feature/dbsshkeys (#262)

* Dev -> Master after v1/v2 cleanup (#261)

* Reverse pull request from master to dev.

Reverse pull request.

* Fix/docker latest tag (#260)

* Dev (#254)

* use functional options pattern to inject logr (#124)

* use functional options pattern to inject logr

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

* cleanup log formatting

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

* allow clean shutdown (#126)

* fix owncloud posix query, log message and provisioning api results (#128)

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

* don't hardcode graphapi endpoint

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

* add support for write handlers (#135)

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

* config: match shadowaccount objectlcass (#136)

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

* Makefile compatiblility (#134)

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update travis.yml (#154)

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

* check owncloud status code is ok (#153)

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

* Bulid and push multiarch docker images (#142)

Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't mix graph and provisioning api (#157)

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

* Reuse http client as much as possible and allow insecure transport (#160)

* Allow using configmaps when deploying in kubernetes (#161)

* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals

* Fix API server json formating (#163) (#164)

* Update README.md (#167)

Fixing bad port in quickstart

* Use port 3893 with ldapsearch in example (#150)

* feat: add flags for ldap listen addresses (#169)

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Assets build fix. (#171)

* Docker build fix.

* Makefile fix.

* update readme, config and deps (#170)

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

* Remove byanke's donation fields

* Database plugins (#133)

This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying

* Mac M1 Support and LDAP Req Attributes (#192)

* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas

* Feature/upgrade ldap library version (#194)

* Stronger, salted paswords using bcrypt. (#195)

* Fixed badges in README file and added a couple improvements (#196)

* fix lock for ownCloud / graph backend (#198)

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

* the config struct is only needed by the config backend (#199)

* Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201)

* fix: keep watch config file when changed,renamed,removed (#189)

I will need to run `glauth.go` through `gofmt` myself but that is fine.

* Returning when unable to start config watcher (#203)

* Introducing goconvey testing and refactoring of config and ... (#204)

* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes

* Allow bind operations with no group provided (#205)

* Support for userPrincipalName binding and browsing. (#206)

* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users

* Rate limit after failed binds (#207)

* Capabilities -- part 2 (#214) + do not dump config at startup

* Attempting migration from Travis to Github Actions

* Migration Step#2

* Migration Step#3

* Migration Step#4

* Migration Step#5

* Migration Step#6

* Migration Step#7

* Migration Step#8

* Migration Step#9

* Migration Step#9

* Migration dev...

* Migration dev...

* Migration dev...

* Migration dev...

* Docker with plugins, first step (#215)

* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI

* - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions

* Plugins now run on distroless (#217)

* Getting rid of Travis CI now that it doesn't support FOSS anymore.

* Internal Stats -- performance view (#221)

* Local merge

* Update CI to forget about bindata

* Forgot to cleanup the cleanup...

* assets: use stdlib "embed" package (#200)

This should make it easier to include GLAuth in other projects using `go get`

* 'Airgapping' web assets for security and preventing breakage (#227)

* v2 -- V2 hierarchy (#228)

* Search refactoring, tree traversal and scope correctness (#229)

Search refactoring, tree traversal and scope correctness.

* Feature/multi cfg (#233)

* Feature/custom attributes (#240)

* Makefiles: platform releases (#241)

* Feature/release script (#242)

* Bug/fix docker build in v2 (#244)

* Fix #246 and #252

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* Cleanup

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* SSH Keys support in database plugins

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* Better plugin build for darwin

* add link to documentation, only discovered it while perusing issues list (#276)

* Plugin: Unix PAM Authentication (https://github.com/glauth/glauth/pull/263) (#277)

* Plugin: Unix PAM Authentication (#263)

* Add plugin using pam authentication

Adds an additional plugin which us authenticating against
the PAM unix backend and exposing users and groups local
to the machine glauth is running on.

This can be used to expose local users for authentication
in other services which support ldap only.

* plugins: pam: Rewrite Bind() to use ldapopshelper

Modernizes the implementation of Bind() to make use of the helper
functions provided by LDAPOpsHelper.

In order to support custom authentication the existing config.User
has received an additional PassAppCustom property which allows to
specify a custom authentication callback for a user.

In case of the PAM backend this will be used to authenticate against
the local PAM database.

* plugins: pam: Rewrite Search() to use ldapopshelper

Modernizes the implementation of Search() to make use of the helper
functions provided by LDAPOpsHelper.

* plugins: pam: Capability through group membership

Adds a configuration option which decides if a user gets the search
capability or not based on the group memberships of a user.

* plugins: pam: Apply formatting

Runs gofmt and go get on all changes done earlier

* plugins: pam: Address feedback from CodeClimate

- reduce code similarity
- document new exports
- address casing of variables and functions
- reduce complexity of FindPosixGroups()
- reduce complexity of FindPosixAccounts()
- fix else branch in ldapopshelper

Co-authored-by: Marius Zwicker <marius.zwicker@mlba-team.de>

* Updated README for pam plugin

* Updated README for pam plugin

Co-authored-by: Marius Zwicker <marius@mlba-team.de>
Co-authored-by: Marius Zwicker <marius.zwicker@mlba-team.de>

* Removing db plugins

* Changing plugind package

* Remove main frmo plugin

* Move plugins to their own repos (#283)

* Plugins release build delegated to plugin Makefile

* Build and push docker containers

* README points to documentation

* Shortened README

* Prometheus exported (#284)

* Prometheus exporter

* Feature/zerolog (#285)

* Zerolog

* Adjusted logging levels

* Structured logging including ldap library

* Feature/check config (#286)

Add config check `--check-config`

* Removed last trace of old docker files

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
Co-authored-by: dlitster <davidlitster@gmail.com>
Co-authored-by: Marius Zwicker <marius@mlba-team.de>
Co-authored-by: Marius Zwicker <marius.zwicker@mlba-team.de>
2022-11-06 20:10:22 -08:00
Chris F Ravenscroft 122c86c446 Merge branch 'master' into dev 2022-11-06 20:03:23 -08:00
Chris F Ravenscroft 0d03b226b6 Removed last trace of old docker files 2022-11-06 20:01:06 -08:00
Chris F Ravenscroft f41b5ee19e Dev to Master for 2.2.0-RC1 (#287)
* Better plugin build for darwin

* add link to documentation, only discovered it while perusing issues list (#276)

* Plugin: Unix PAM Authentication (https://github.com/glauth/glauth/pull/263) (#277)

* Plugin: Unix PAM Authentication (#263)

* Add plugin using pam authentication

Adds an additional plugin which us authenticating against
the PAM unix backend and exposing users and groups local
to the machine glauth is running on.

This can be used to expose local users for authentication
in other services which support ldap only.

* plugins: pam: Rewrite Bind() to use ldapopshelper

Modernizes the implementation of Bind() to make use of the helper
functions provided by LDAPOpsHelper.

In order to support custom authentication the existing config.User
has received an additional PassAppCustom property which allows to
specify a custom authentication callback for a user.

In case of the PAM backend this will be used to authenticate against
the local PAM database.

* plugins: pam: Rewrite Search() to use ldapopshelper

Modernizes the implementation of Search() to make use of the helper
functions provided by LDAPOpsHelper.

* plugins: pam: Capability through group membership

Adds a configuration option which decides if a user gets the search
capability or not based on the group memberships of a user.

* plugins: pam: Apply formatting

Runs gofmt and go get on all changes done earlier

* plugins: pam: Address feedback from CodeClimate

- reduce code similarity
- document new exports
- address casing of variables and functions
- reduce complexity of FindPosixGroups()
- reduce complexity of FindPosixAccounts()
- fix else branch in ldapopshelper

Co-authored-by: Marius Zwicker <marius.zwicker@mlba-team.de>

* Updated README for pam plugin

* Updated README for pam plugin

Co-authored-by: Marius Zwicker <marius@mlba-team.de>
Co-authored-by: Marius Zwicker <marius.zwicker@mlba-team.de>

* Removing db plugins

* Changing plugind package

* Remove main frmo plugin

* Move plugins to their own repos (#283)

* Plugins release build delegated to plugin Makefile

* Build and push docker containers

* README points to documentation

* Shortened README

* Prometheus exported (#284)

* Prometheus exporter

* Feature/zerolog (#285)

* Zerolog

* Adjusted logging levels

* Structured logging including ldap library

* Feature/check config (#286)

Add config check `--check-config`

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
Co-authored-by: dlitster <davidlitster@gmail.com>
Co-authored-by: Marius Zwicker <marius@mlba-team.de>
Co-authored-by: Marius Zwicker <marius.zwicker@mlba-team.de>
2022-11-06 19:57:58 -08:00
Chris F Ravenscroft aad166b3ec Merge branch 'master' into dev 2022-11-06 19:50:51 -08:00
Chris F Ravenscroft 6b14714eb4 Feature/check config (#286)
Add config check `--check-config`
2022-11-06 00:36:51 -07:00
Chris F Ravenscroft 3db5c5e54a Feature/zerolog (#285)
* Zerolog

* Adjusted logging levels

* Structured logging including ldap library
2022-11-06 00:22:14 -07:00
Chris F Ravenscroft 1a78b51174 Prometheus exported (#284)
* Prometheus exporter
2022-10-30 23:48:16 -07:00
Chris F Ravenscroft 9865de68ac Shortened README 2022-10-29 22:56:53 -07:00
Chris F Ravenscroft 32750d2abd Move plugins to their own repos (#283)
* Plugins release build delegated to plugin Makefile

* Build and push docker containers

* README points to documentation
2022-10-29 15:16:57 -07:00
Chris F Ravenscroft 021324c775 Remove main frmo plugin 2022-10-17 20:47:57 -07:00
Chris F Ravenscroft edaddca4e7 Changing plugind package 2022-10-17 20:46:02 -07:00
Chris F Ravenscroft 1af3cf4f69 Removing db plugins 2022-10-17 20:32:38 -07:00
Chris F Ravenscroft 3e2e24cadb Plugin: Unix PAM Authentication (https://github.com/glauth/glauth/pull/263) (#277)
* Plugin: Unix PAM Authentication (#263)

* Add plugin using pam authentication

Adds an additional plugin which us authenticating against
the PAM unix backend and exposing users and groups local
to the machine glauth is running on.

This can be used to expose local users for authentication
in other services which support ldap only.

* plugins: pam: Rewrite Bind() to use ldapopshelper

Modernizes the implementation of Bind() to make use of the helper
functions provided by LDAPOpsHelper.

In order to support custom authentication the existing config.User
has received an additional PassAppCustom property which allows to
specify a custom authentication callback for a user.

In case of the PAM backend this will be used to authenticate against
the local PAM database.

* plugins: pam: Rewrite Search() to use ldapopshelper

Modernizes the implementation of Search() to make use of the helper
functions provided by LDAPOpsHelper.

* plugins: pam: Capability through group membership

Adds a configuration option which decides if a user gets the search
capability or not based on the group memberships of a user.

* plugins: pam: Apply formatting

Runs gofmt and go get on all changes done earlier

* plugins: pam: Address feedback from CodeClimate

- reduce code similarity
- document new exports
- address casing of variables and functions
- reduce complexity of FindPosixGroups()
- reduce complexity of FindPosixAccounts()
- fix else branch in ldapopshelper

Co-authored-by: Marius Zwicker <marius.zwicker@mlba-team.de>

* Updated README for pam plugin

* Updated README for pam plugin

Co-authored-by: Marius Zwicker <marius@mlba-team.de>
Co-authored-by: Marius Zwicker <marius.zwicker@mlba-team.de>
2022-08-14 20:58:36 -07:00
dlitster 249388fdd1 add link to documentation, only discovered it while perusing issues list (#276) 2022-08-14 16:50:50 -07:00
Chris F Ravenscroft 45651645e7 Better plugin build for darwin 2022-06-11 20:53:01 +00:00
Chris F Ravenscroft 42ff05f141 Feature/dbsshkeys (#262)
* Dev -> Master after v1/v2 cleanup (#261)

* Reverse pull request from master to dev.

Reverse pull request.

* Fix/docker latest tag (#260)

* Dev (#254)

* use functional options pattern to inject logr (#124)

* use functional options pattern to inject logr

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

* cleanup log formatting

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

* allow clean shutdown (#126)

* fix owncloud posix query, log message and provisioning api results (#128)

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

* don't hardcode graphapi endpoint

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

* add support for write handlers (#135)

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

* config: match shadowaccount objectlcass (#136)

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

* Makefile compatiblility (#134)

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update travis.yml (#154)

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

* check owncloud status code is ok (#153)

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

* Bulid and push multiarch docker images (#142)

Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't mix graph and provisioning api (#157)

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

* Reuse http client as much as possible and allow insecure transport (#160)

* Allow using configmaps when deploying in kubernetes (#161)

* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals

* Fix API server json formating (#163) (#164)

* Update README.md (#167)

Fixing bad port in quickstart

* Use port 3893 with ldapsearch in example (#150)

* feat: add flags for ldap listen addresses (#169)

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Assets build fix. (#171)

* Docker build fix.

* Makefile fix.

* update readme, config and deps (#170)

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

* Remove byanke's donation fields

* Database plugins (#133)

This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying

* Mac M1 Support and LDAP Req Attributes (#192)

* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas

* Feature/upgrade ldap library version (#194)

* Stronger, salted paswords using bcrypt. (#195)

* Fixed badges in README file and added a couple improvements (#196)

* fix lock for ownCloud / graph backend (#198)

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

* the config struct is only needed by the config backend (#199)

* Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201)

* fix: keep watch config file when changed,renamed,removed (#189)

I will need to run `glauth.go` through `gofmt` myself but that is fine.

* Returning when unable to start config watcher (#203)

* Introducing goconvey testing and refactoring of config and ... (#204)

* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes

* Allow bind operations with no group provided (#205)

* Support for userPrincipalName binding and browsing. (#206)

* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users

* Rate limit after failed binds (#207)

* Capabilities -- part 2 (#214) + do not dump config at startup

* Attempting migration from Travis to Github Actions

* Migration Step#2

* Migration Step#3

* Migration Step#4

* Migration Step#5

* Migration Step#6

* Migration Step#7

* Migration Step#8

* Migration Step#9

* Migration Step#9

* Migration dev...

* Migration dev...

* Migration dev...

* Migration dev...

* Docker with plugins, first step (#215)

* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI

* - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions

* Plugins now run on distroless (#217)

* Getting rid of Travis CI now that it doesn't support FOSS anymore.

* Internal Stats -- performance view (#221)

* Local merge

* Update CI to forget about bindata

* Forgot to cleanup the cleanup...

* assets: use stdlib "embed" package (#200)

This should make it easier to include GLAuth in other projects using `go get`

* 'Airgapping' web assets for security and preventing breakage (#227)

* v2 -- V2 hierarchy (#228)

* Search refactoring, tree traversal and scope correctness (#229)

Search refactoring, tree traversal and scope correctness.

* Feature/multi cfg (#233)

* Feature/custom attributes (#240)

* Makefiles: platform releases (#241)

* Feature/release script (#242)

* Bug/fix docker build in v2 (#244)

* Fix #246 and #252

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* Cleanup

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* SSH Keys support in database plugins

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2022-05-08 19:33:27 -07:00
Chris F Ravenscroft 598d746ee8 Dev -> Master after v1/v2 cleanup (#261)
* Reverse pull request from master to dev.

Reverse pull request.

* Fix/docker latest tag (#260)

* Dev (#254)

* use functional options pattern to inject logr (#124)

* use functional options pattern to inject logr

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

* cleanup log formatting

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

* allow clean shutdown (#126)

* fix owncloud posix query, log message and provisioning api results (#128)

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

* don't hardcode graphapi endpoint

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

* add support for write handlers (#135)

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

* config: match shadowaccount objectlcass (#136)

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

* Makefile compatiblility (#134)

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update travis.yml (#154)

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

* check owncloud status code is ok (#153)

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

* Bulid and push multiarch docker images (#142)

Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't mix graph and provisioning api (#157)

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

* Reuse http client as much as possible and allow insecure transport (#160)

* Allow using configmaps when deploying in kubernetes (#161)

* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals

* Fix API server json formating (#163) (#164)

* Update README.md (#167)

Fixing bad port in quickstart

* Use port 3893 with ldapsearch in example (#150)

* feat: add flags for ldap listen addresses (#169)

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Assets build fix. (#171)

* Docker build fix.

* Makefile fix.

* update readme, config and deps (#170)

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

* Remove byanke's donation fields

* Database plugins (#133)

This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying

* Mac M1 Support and LDAP Req Attributes (#192)

* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas

* Feature/upgrade ldap library version (#194)

* Stronger, salted paswords using bcrypt. (#195)

* Fixed badges in README file and added a couple improvements (#196)

* fix lock for ownCloud / graph backend (#198)

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

* the config struct is only needed by the config backend (#199)

* Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201)

* fix: keep watch config file when changed,renamed,removed (#189)

I will need to run `glauth.go` through `gofmt` myself but that is fine.

* Returning when unable to start config watcher (#203)

* Introducing goconvey testing and refactoring of config and ... (#204)

* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes

* Allow bind operations with no group provided (#205)

* Support for userPrincipalName binding and browsing. (#206)

* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users

* Rate limit after failed binds (#207)

* Capabilities -- part 2 (#214) + do not dump config at startup

* Attempting migration from Travis to Github Actions

* Migration Step#2

* Migration Step#3

* Migration Step#4

* Migration Step#5

* Migration Step#6

* Migration Step#7

* Migration Step#8

* Migration Step#9

* Migration Step#9

* Migration dev...

* Migration dev...

* Migration dev...

* Migration dev...

* Docker with plugins, first step (#215)

* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI

* - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions

* Plugins now run on distroless (#217)

* Getting rid of Travis CI now that it doesn't support FOSS anymore.

* Internal Stats -- performance view (#221)

* Local merge

* Update CI to forget about bindata

* Forgot to cleanup the cleanup...

* assets: use stdlib "embed" package (#200)

This should make it easier to include GLAuth in other projects using `go get`

* 'Airgapping' web assets for security and preventing breakage (#227)

* v2 -- V2 hierarchy (#228)

* Search refactoring, tree traversal and scope correctness (#229)

Search refactoring, tree traversal and scope correctness.

* Feature/multi cfg (#233)

* Feature/custom attributes (#240)

* Makefiles: platform releases (#241)

* Feature/release script (#242)

* Bug/fix docker build in v2 (#244)

* Fix #246 and #252

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* Cleanup

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2022-05-07 12:39:08 -07:00
Chris F Ravenscroft 6800da8bf8 Merge branch 'master' into dev 2022-05-07 12:29:52 -07:00
Chris F Ravenscroft 57ee724556 Fix/docker latest tag (#260)
* Dev (#254)

* use functional options pattern to inject logr (#124)

* use functional options pattern to inject logr

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

* cleanup log formatting

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

* allow clean shutdown (#126)

* fix owncloud posix query, log message and provisioning api results (#128)

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

* don't hardcode graphapi endpoint

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

* add support for write handlers (#135)

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

* config: match shadowaccount objectlcass (#136)

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

* Makefile compatiblility (#134)

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update travis.yml (#154)

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

* check owncloud status code is ok (#153)

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

* Bulid and push multiarch docker images (#142)

Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't mix graph and provisioning api (#157)

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

* Reuse http client as much as possible and allow insecure transport (#160)

* Allow using configmaps when deploying in kubernetes (#161)

* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals

* Fix API server json formating (#163) (#164)

* Update README.md (#167)

Fixing bad port in quickstart

* Use port 3893 with ldapsearch in example (#150)

* feat: add flags for ldap listen addresses (#169)

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Assets build fix. (#171)

* Docker build fix.

* Makefile fix.

* update readme, config and deps (#170)

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

* Remove byanke's donation fields

* Database plugins (#133)

This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying

* Mac M1 Support and LDAP Req Attributes (#192)

* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas

* Feature/upgrade ldap library version (#194)

* Stronger, salted paswords using bcrypt. (#195)

* Fixed badges in README file and added a couple improvements (#196)

* fix lock for ownCloud / graph backend (#198)

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

* the config struct is only needed by the config backend (#199)

* Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201)

* fix: keep watch config file when changed,renamed,removed (#189)

I will need to run `glauth.go` through `gofmt` myself but that is fine.

* Returning when unable to start config watcher (#203)

* Introducing goconvey testing and refactoring of config and ... (#204)

* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes

* Allow bind operations with no group provided (#205)

* Support for userPrincipalName binding and browsing. (#206)

* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users

* Rate limit after failed binds (#207)

* Capabilities -- part 2 (#214) + do not dump config at startup

* Attempting migration from Travis to Github Actions

* Migration Step#2

* Migration Step#3

* Migration Step#4

* Migration Step#5

* Migration Step#6

* Migration Step#7

* Migration Step#8

* Migration Step#9

* Migration Step#9

* Migration dev...

* Migration dev...

* Migration dev...

* Migration dev...

* Docker with plugins, first step (#215)

* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI

* - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions

* Plugins now run on distroless (#217)

* Getting rid of Travis CI now that it doesn't support FOSS anymore.

* Internal Stats -- performance view (#221)

* Local merge

* Update CI to forget about bindata

* Forgot to cleanup the cleanup...

* assets: use stdlib "embed" package (#200)

This should make it easier to include GLAuth in other projects using `go get`

* 'Airgapping' web assets for security and preventing breakage (#227)

* v2 -- V2 hierarchy (#228)

* Search refactoring, tree traversal and scope correctness (#229)

Search refactoring, tree traversal and scope correctness.

* Feature/multi cfg (#233)

* Feature/custom attributes (#240)

* Makefiles: platform releases (#241)

* Feature/release script (#242)

* Bug/fix docker build in v2 (#244)

* Fix #246 and #252

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>

* Cleanup

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2022-05-07 12:28:51 -07:00
Chris F Ravenscroft ea4cd57233 Reverse pull request from master to dev.
Reverse pull request.
2022-02-27 17:29:42 -08:00
Chris F Ravenscroft 5d79fd9b03 Merge branch 'dev' into master 2022-02-27 17:28:33 -08:00
Chris F Ravenscroft 385cc335ca Dev (#254)
* use functional options pattern to inject logr (#124)

* use functional options pattern to inject logr

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

* cleanup log formatting

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

* allow clean shutdown (#126)

* fix owncloud posix query, log message and provisioning api results (#128)

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

* don't hardcode graphapi endpoint

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

* add support for write handlers (#135)

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

* config: match shadowaccount objectlcass (#136)

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

* Makefile compatiblility (#134)

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update travis.yml (#154)

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

* check owncloud status code is ok (#153)

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

* Bulid and push multiarch docker images (#142)

Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* don't mix graph and provisioning api (#157)

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

* Reuse http client as much as possible and allow insecure transport (#160)

* Allow using configmaps when deploying in kubernetes (#161)

* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals

* Fix API server json formating (#163) (#164)

* Update README.md (#167)

Fixing bad port in quickstart

* Use port 3893 with ldapsearch in example (#150)

* feat: add flags for ldap listen addresses (#169)

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>

* Assets build fix. (#171)

* Docker build fix.

* Makefile fix.

* update readme, config and deps (#170)

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

* Remove byanke's donation fields

* Database plugins (#133)

This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying

* Mac M1 Support and LDAP Req Attributes (#192)

* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas

* Feature/upgrade ldap library version (#194)

* Stronger, salted paswords using bcrypt. (#195)

* Fixed badges in README file and added a couple improvements (#196)

* fix lock for ownCloud / graph backend (#198)

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

* the config struct is only needed by the config backend (#199)

* Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201)

* fix: keep watch config file when changed,renamed,removed (#189)

I will need to run `glauth.go` through `gofmt` myself but that is fine.

* Returning when unable to start config watcher (#203)

* Introducing goconvey testing and refactoring of config and ... (#204)

* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes

* Allow bind operations with no group provided (#205)

* Support for userPrincipalName binding and browsing. (#206)

* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users

* Rate limit after failed binds (#207)

* Capabilities -- part 2 (#214) + do not dump config at startup

* Attempting migration from Travis to Github Actions

* Migration Step#2

* Migration Step#3

* Migration Step#4

* Migration Step#5

* Migration Step#6

* Migration Step#7

* Migration Step#8

* Migration Step#9

* Migration Step#9

* Migration dev...

* Migration dev...

* Migration dev...

* Migration dev...

* Docker with plugins, first step (#215)

* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI

* - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions

* Plugins now run on distroless (#217)

* Getting rid of Travis CI now that it doesn't support FOSS anymore.

* Internal Stats -- performance view (#221)

* Local merge

* Update CI to forget about bindata

* Forgot to cleanup the cleanup...

* assets: use stdlib "embed" package (#200)

This should make it easier to include GLAuth in other projects using `go get`

* 'Airgapping' web assets for security and preventing breakage (#227)

* v2 -- V2 hierarchy (#228)

* Search refactoring, tree traversal and scope correctness (#229)

Search refactoring, tree traversal and scope correctness.

* Feature/multi cfg (#233)

* Feature/custom attributes (#240)

* Makefiles: platform releases (#241)

* Feature/release script (#242)

* Bug/fix docker build in v2 (#244)

* Fix #246 and #252

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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2022-02-27 16:35:57 -08:00
Chris F Ravenscroft 851c21df04 Merge branch 'master' into dev 2022-02-27 16:34:44 -08:00
Chris F Ravenscroft ceefc66dbb Fix #246 and #252 2022-01-30 13:31:40 -08:00
Chris F Ravenscroft e26219c2f4 Dev (#245) -- Docker fix for V2 hierarchy
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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2021-11-26 17:42:20 -08:00
Chris F Ravenscroft 2222a791e4 Merge branch 'master' into dev 2021-11-27 01:01:24 +00:00
Chris F Ravenscroft eef3a5c6a0 Bug/fix docker build in v2 (#244) 2021-11-26 16:37:47 -08:00
Chris F Ravenscroft e7195ed983 Dev to Master 2.1.0 RC1 (#243)
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: fanlix <fanlix@gmail.com>
Co-authored-by: Hank Donnay <hdonnay@gmail.com>
2021-11-25 17:56:59 -08:00
Chris F Ravenscroft 1299a6d002 Deprecated glauth.go merge from master 2021-11-26 01:22:04 +00:00
Chris F Ravenscroft 95ac9708ed Feature/release script (#242) 2021-11-25 16:35:36 -08:00
Chris F Ravenscroft 363cd76fa2 Makefiles: platform releases (#241) 2021-11-24 23:55:59 -08:00
Chris F Ravenscroft 82bc471735 Feature/custom attributes (#240) 2021-11-24 18:22:42 -08:00
Chris F Ravenscroft e1e586f464 Feature/multi cfg (#233) 2021-11-06 15:43:25 -07:00
Chris F Ravenscroft 31fbd790e8 Search refactoring, tree traversal and scope correctness (#229)
Search refactoring, tree traversal and scope correctness.
2021-10-25 13:41:22 -07:00
Chris F Ravenscroft ef3151c287 v2 -- V2 hierarchy (#228) 2021-10-20 18:13:45 -07:00
Chris F Ravenscroft de3d1cec1c 'Airgapping' web assets for security and preventing breakage (#227) 2021-10-20 12:07:42 -07:00
Hank Donnay 7faac6df69 assets: use stdlib "embed" package (#200)
This should make it easier to include GLAuth in other projects using `go get`
2021-10-20 11:26:04 -07:00
Chris F Ravenscroft e86d6000d9 Forgot to cleanup the cleanup... 2021-10-20 11:09:42 -07:00
Chris F Ravenscroft c7098406d0 Update CI to forget about bindata 2021-10-20 10:54:48 -07:00
Chris F Ravenscroft 911e933020 Merge branch 'hdonnay-embed' into dev 2021-10-20 10:28:44 -07:00
Chris F Ravenscroft 53eddd67c9 Local merge 2021-10-20 10:28:36 -07:00
Chris F Ravenscroft c2360259af Internal Stats -- performance view (#221) 2021-10-04 23:31:13 -07:00
Chris F Ravenscroft 7e49d25591 Merge branch 'dev' of github.com:glauth/glauth into dev 2021-09-12 04:46:15 +00:00
Chris F Ravenscroft 7267d7b129 Getting rid of Travis CI now that it doesn't support FOSS anymore. 2021-09-12 04:45:44 +00:00
Chris F Ravenscroft 0e8937c61e Plugins now run on distroless (#217) 2021-09-11 21:34:55 -07:00
Chris F Ravenscroft fd85683ff0 Merge branch 'dev' of github.com:glauth/glauth into dev 2021-09-06 23:37:40 -07:00
Chris F Ravenscroft 2b45aefd74 - Now creating two docker images, including one with plugins
- Moving from Travis CI to GitHub Actions
2021-09-06 23:34:59 -07:00
Chris F Ravenscroft 05c537dabd Docker with plugins, first step (#215)
* Docker with plugins, first step

* Build better docker images, including plugins

* Merge back from dev to feature branch step #2

* Added capabilities to CI
2021-09-06 23:28:30 -07:00
Chris F Ravenscroft 8c8bc06c3b Migration dev... 2021-09-06 21:29:52 -07:00
Chris F Ravenscroft fed91e4aef Migration dev... 2021-09-06 21:24:29 -07:00
Chris F Ravenscroft 7be05404e1 Migration dev... 2021-09-06 21:07:03 -07:00
Chris F Ravenscroft af9062f1a1 Migration dev... 2021-09-06 21:05:16 -07:00
Chris F Ravenscroft aed137909a Migration Step#9 2021-09-06 21:00:45 -07:00
Chris F Ravenscroft 846f0509d6 Migration Step#9 2021-09-06 20:54:26 -07:00
Chris F Ravenscroft 06afbdcee5 Migration Step#8 2021-09-06 20:52:26 -07:00
Chris F Ravenscroft 2e0f7f7335 Migration Step#7 2021-09-06 20:50:15 -07:00
Chris F Ravenscroft b07cd1f256 Migration Step#6 2021-09-06 19:03:22 -07:00
Chris F Ravenscroft 2ccb8ce02e Migration Step#5 2021-09-06 18:57:27 -07:00
Chris F Ravenscroft 5dfbbb2f4b Migration Step#4 2021-09-06 18:31:30 -07:00
Chris F Ravenscroft 3a30656c18 Migration Step#3 2021-09-06 18:29:48 -07:00
Chris F Ravenscroft 3f53bb983a Migration Step#2 2021-09-06 18:24:21 -07:00
Chris F Ravenscroft 3b7a548389 Attempting migration from Travis to Github Actions 2021-09-06 18:22:58 -07:00
Chris F Ravenscroft 0ec6f15957 Capabilities -- part 2 (#214) + do not dump config at startup 2021-09-05 20:54:47 -07:00
Chris F Ravenscroft be26675ef8 Revert "Capabilities." from Master. (#211)
This reverts commit 8ef3e2b8d8.
2021-08-23 00:44:23 -07:00
Chris F Ravenscroft 8ef3e2b8d8 Capabilities. (#210) 2021-08-23 00:42:21 -07:00
Chris F Ravenscroft ccb839f0a5 Create and push docker image using podman (#209) 2021-08-22 13:46:49 -07:00
Chris F Ravenscroft 8223029345 Rate limit after failed binds (#207) 2021-08-12 12:59:14 -07:00
Chris F Ravenscroft 7c7934aea3 Support for userPrincipalName binding and browsing. (#206)
* Support for userPrincipalName binding and browsing.

* Fixed travis CI test for userprincipalname

* fixing travis tests for more users
2021-08-11 18:12:45 -07:00
Chris F Ravenscroft 9c9294485b Allow bind operations with no group provided (#205) 2021-08-11 15:45:30 -07:00
Chris F Ravenscroft 4cf38893ac Introducing goconvey testing and refactoring of config and ... (#204)
* Introducing goconvey testing and refactoring of config and plugin backends.

* gofmt, oops

* Refactored re-insertion of requested attributes
2021-08-10 20:48:14 -07:00
Chris F Ravenscroft 674a6bdbf4 Returning when unable to start config watcher (#203) 2021-08-08 17:31:06 -07:00
fanlix 7b082dccb8 fix: keep watch config file when changed,renamed,removed (#189)
I will need to run `glauth.go` through `gofmt` myself but that is fine.
2021-08-02 17:23:41 -07:00
Chris F Ravenscroft 10b0191818 Variable "unixid" is now respectively "UIDNumber" and "GIDNumber" for… (#201) 2021-08-02 17:20:42 -07:00
Jörn Friedrich Dreyer 53ddd3c4f4 the config struct is only needed by the config backend (#199) 2021-07-29 14:50:47 -07:00
Jörn Friedrich Dreyer 2065fc39f4 fix lock for ownCloud / graph backend (#198)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2021-07-29 20:58:42 +02:00
Chris F Ravenscroft e2724eadc2 Fixed badges in README file and added a couple improvements (#196) 2021-07-25 22:09:45 -07:00
Chris F Ravenscroft 9c197cc35e Stronger, salted paswords using bcrypt. (#195) 2021-07-25 17:00:20 -07:00
Chris F Ravenscroft 25a1648706 Feature/upgrade ldap library version (#194) 2021-07-25 13:01:31 -07:00
Chris F Ravenscroft cbb84b8641 Mac M1 Support and LDAP Req Attributes (#192)
* Mac M1 Support

* Augmented root DSS and schema discovery based on content of schema directory

* LDAP workaround where req. attribute gets injected in response now works with combined filters

* SubSchema query can return a minimal set, freeipa or openldap's schemas
2021-07-25 00:36:18 -07:00
Chris F Ravenscroft 2eb2215ae4 Database plugins (#133)
This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying
2021-07-17 11:45:36 -07:00
Jörn Friedrich Dreyer 225f1b441b Merge branch 'master' into dev 2021-02-27 21:19:34 +01:00
Ben Yanke bf5ddba2be Remove byanke's donation fields 2021-02-27 07:50:23 -06:00
Jörn Friedrich Dreyer fcf784ecf0 update readme, config and deps (#170)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2021-02-25 21:42:50 +01:00
Peter Heise 07763f52e6 Assets build fix. (#171)
* Docker build fix.

* Makefile fix.
2021-02-24 19:29:07 +01:00
Márk Sági-Kazár 262465d63b feat: add flags for ldap listen addresses (#169)
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-02-16 19:31:08 +01:00
Lutz Horn f28f1a115b Use port 3893 with ldapsearch in example (#150) 2021-02-11 21:37:47 +01:00
Clement JACOB f4ceb0c3d4 Update README.md (#167)
Fixing bad port in quickstart
2021-01-14 18:52:16 +01:00
Thibault Soubiran 9fb9c87625 Fix API server json formating (#163) (#164) 2021-01-11 13:15:06 +01:00
Nicolai Willems faed82ad44 Allow using configmaps when deploying in kubernetes (#161)
* Ensure config watcher also works in kubernetes

When using configmaps in kubernetes, the file is a symlink, and then
file-watcher is not opdated with a write event. Instead it receives a
CHMOD and a REMOVE event.

This change adds two things
1) Removal of the current wacther and adding of a new watcher for the
same path
2) The do-reload conditional is updated to also include the remove
event.

* Align write and removed conditionals
2020-12-08 22:27:37 +01:00
Benedikt Kulmann fd3ac7e4bb Reuse http client as much as possible and allow insecure transport (#160) 2020-11-10 13:46:27 +01:00
Jörn Friedrich Dreyer 4f029234b2 don't mix graph and provisioning api (#157)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-10-30 14:08:13 +01:00
Jairo Llopis d6ba7d0cbc Bulid and push multiarch docker images (#142)
Fix #141.

With this change, a github action is added that builds a multiarch docker image on every commit, supporting x86_64, aarch64 and arm/v7.

When the action is triggered on a release or tag, it also uploads the versioned image to github container image registry.

Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-10-05 23:24:13 +02:00
Jörn Friedrich Dreyer 4d42af8aac check owncloud status code is ok (#153)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-10-05 22:19:19 +02:00
Jörn Friedrich Dreyer fda48f5524 update travis.yml (#154)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-10-05 21:57:20 +02:00
Zhou Wenzong b7cfc11f5c Makefile compatiblility (#134)
Co-authored-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-05-15 13:53:10 +02:00
Jörn Friedrich Dreyer bbb381a68e config: match shadowaccount objectlcass (#136)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-05-15 13:41:00 +02:00
Jörn Friedrich Dreyer 28136302b7 add support for write handlers (#135)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-05-15 12:49:53 +02:00
Jörn Friedrich Dreyer 4e600736f2 don't hardcode graphapi endpoint
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-05-07 18:09:26 +02:00
Jörn Friedrich Dreyer 2d4f5d5476 fix owncloud posix query, log message and provisioning api results (#128)
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-02-28 17:01:18 +01:00
Jörn Friedrich Dreyer 29cf021694 allow clean shutdown (#126) 2020-02-27 10:26:48 +01:00
Jörn Friedrich Dreyer 592fdf4e79 use functional options pattern to inject logr (#124)
* use functional options pattern to inject logr

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

* cleanup log formatting

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2020-02-26 15:01:31 +01:00
153 changed files with 10430 additions and 3091 deletions
+54
View File
@@ -0,0 +1,54 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/go
{
"name": "glauth",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/go:0-1.19-bullseye",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"docker-in-docker": {
"version": "latest",
"moby": true
},
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"packages": "oathtool,ldap-utils"
}
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
"settings": {},
"extensions": [
"golang.go",
"ms-azuretools.vscode-docker"
]
}
},
"containerEnv": {
"REPO": "glauth"
},
"capAdd": ["SYS_PTRACE"],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [9000],
// Use 'portsAttributes' to set default properties for specific forwarded ports.
// More info: https://containers.dev/implementors/json_reference/#port-attributes
"portsAttributes": {
"3893": {
"label": "LDAP",
"onAutoForward": "notify"
},
"5555": {
"label": "Web UI",
"onAutoForward": "openBrowser"
}
},
"postCreateCommand": "cd v2 && make setup"
}
+2 -2
View File
@@ -14,9 +14,9 @@ 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
CI system. Please use these to your advantage. In particular, the Github Actions
integration tests rely on the LDAP queries returning with a set result,
so if your changes will change the output, Travis will likely fail.
so if your changes will change the output, CI 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
+39
View File
@@ -0,0 +1,39 @@
name: ci
on:
pull_request:
push:
branches:
- dev
jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
go-version: [1.19.x, 1.20.x]
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Updating packages database
run: sudo apt-get -qq update
- name: Install CLI Dependencies
run: sudo apt-get install -y ldap-utils oathtool libpam0g-dev
- name: Checkout code
uses: actions/checkout@v2
- name: Fetch Go dependencies
working-directory: ./v2
run: go get -t -v ./...
- name: Fail if a .go file hasn't been formatted with 'go fmt'
working-directory: ./v2
run: diff -u <(echo -n) <(gofmt -d ./)
- name: Full compile test
working-directory: ./v2
run: make fast
- name: Output hashes to the build log
working-directory: ./v2
run: cat bin/*/glauth.sha256
- name: Integration test with compiled binary
working-directory: ./v2
run: make test
+41
View File
@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [ "master", "dev" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "46 1 * * 3"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ javascript ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
@@ -0,0 +1,22 @@
# This workflow was added by CodeSee. Learn more at https://codesee.io/
# This is v2.0 of this workflow file
on:
push:
branches:
- master
pull_request_target:
types: [opened, synchronize, reopened]
name: CodeSee
permissions: read-all
jobs:
codesee:
runs-on: ubuntu-latest
continue-on-error: true
name: Analyze the repo with CodeSee
steps:
- uses: Codesee-io/codesee-action@v2
with:
codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }}
-41
View File
@@ -1,41 +0,0 @@
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 .
+24
View File
@@ -0,0 +1,24 @@
on:
pull_request:
push:
branches:
- dev
permissions:
contents: write
pull-requests: write
name: release-please
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: google-github-actions/release-please-action@v3
with:
release-type: go
package-name: GLAuth
path: v2
default-branch: dev
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
+5 -19
View File
@@ -1,20 +1,6 @@
# Ignore default config file for testing
glauth.cfg
# Ignore the built binary during testing
glauth
# Ignore generated certs in this folder
certs/
# Built binaries
bin
pkg/assets/bindata.go
.*.swp
# Ignoring travis CLI tool logs
*travis.log
certs/
v2/certs/
v2/bin
v2/docker/assets/linux
v2/gl.db
.vscode/
+12
View File
@@ -0,0 +1,12 @@
[submodule "v2/pkg/plugins/glauth-sqlite"]
path = v2/pkg/plugins/glauth-sqlite
url = https://github.com/glauth/glauth-sqlite
[submodule "v2/pkg/plugins/glauth-mysql"]
path = v2/pkg/plugins/glauth-mysql
url = https://github.com/glauth/glauth-mysql
[submodule "v2/pkg/plugins/glauth-postgres"]
path = v2/pkg/plugins/glauth-postgres
url = https://github.com/glauth/glauth-postgres
[submodule "v2/pkg/plugins/glauth-pam"]
path = v2/pkg/plugins/glauth-pam
url = https://github.com/glauth/glauth-pam
-81
View File
@@ -1,81 +0,0 @@
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.13.x # older version
- 1.14.x # last version
- 1.15.x # current version
# Speed up git clones
git:
depth: 1
# Anything in before_script that returns a nonzero exit code will
# flunk the build and immediately stop. It's sorta like having
# set -e enabled in bash.
before_script:
- travis_retry sudo apt-get -qq update && sudo apt-get install -y ldap-utils oathtool # need this for integration tests
- travis_retry sudo apt-get -qq update && sudo apt-get install -y go-bindata # need this for makefile
- GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) # All the .go files, excluding vendor/
#- travis_retry go get github.com/golang/lint/golint # Linter
#- travis_retry go get honnef.co/go/tools/cmd/megacheck # Badass static analyzer/linter
# - travis_retry go get github.com/fzipp/gocyclo # Not sure what this is
# Run bindata for assets
- make bindata
# Fetch dependencies
- travis_retry go get -t -v ./...
script:
- env # Dump the env to make debugging easier
- diff -u <(echo -n) <(gofmt -d ./) # Fail if a .go file hasn't been formatted with 'go fmt'
# - go vet ./... " # go vet is the official Go static analyzer
# - go test -v -race ./... # Run all the tests with the race detector enabled
# - megacheck ./... # "go vet on steroids" + linter
# - gocyclo -over 19 $GO_FILES # forbid code with huge functions
# - golint -set_exit_status $(go list ./...) # one last linter
# Unit tests
# - tbd - use 'go test' here when we have unit tests written. example command below. This will generate output for codecov
# - go test -race -coverprofile=coverage.txt -covermode=atomic
# Full compile test
# - go build -o ./glauth glauth.go bindata.go ldapbackend.go webapi.go configbackend.go
- make all
# Output hashes to the build log
- cat bin/*.sha256
# Integration test with compiled binary
- make test
# after_success:
# - bash <(curl -s https://codecov.io/bash)
deploy:
provider: releases
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"
cleanup: true
on:
branch: master
tags: true
condition: $TRAVIS_GO_VERSION =~ ^1\.15
repo: glauth/glauth
-66
View File
@@ -1,66 +0,0 @@
#################
# Build Step
#################
FROM golang:alpine as build
MAINTAINER Ben Yanke <ben@benyanke.com>
# Setup work env
RUN mkdir /app /tmp/gocode
ADD . /app/
WORKDIR /app
# Required envs for GO
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 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 ./...
# Build and copy final result
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 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
ADD sample-simple.cfg /app/config/config.cfg
# Copy binary from build container
COPY --from=build /app/glauth /app/glauth
# Copy docker specific scripts from build container
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
# Expose web and LDAP ports
EXPOSE 389 636 5555
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/bin/sh", "/app/docker/start.sh"]
-100
View File
@@ -1,100 +0,0 @@
VERSION=$(shell bin/glauth64 --version)
GIT_COMMIT=$(shell git rev-list -1 HEAD )
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)
# this=1 if the current commit is the tagged commit (ie, if this is a release build)
GIT_IS_TAG_COMMIT=$(shell git describe --abbrev=0 --tags > /dev/null 2> /dev/null && echo "1" || echo "0")
# Used when a tag isn't available
GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
# Build variables
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
#####################
# Build and run - used for development
run: setup devrun cleanup
# Run the integration test on linux64 (eventually allow the binary to be set)
test: runtest
# Run build process for all binaries
all: setup binaries verify cleanup
# Run build process for only linux64
fast: setup linux64 verify cleanup
# list of binary formats to build
binaries: linux32 linux64 linuxarm32 linuxarm64 darwin64 win32 win64
# Setup commands to always run
setup: bindata getdeps format
#####################
# Subcommands
#####################
# Run integration test
runtest:
./scripts/travis/integration-test.sh cleanup
# Get all dependencies
getdeps:
go get -d ./...
updatetest:
./scripts/travis/integration-test.sh
bindata:
go 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 pkg/assets/bindata.go
format:
go fmt
devrun:
go run ${BUILD_FILES} -c sample-simple.cfg
linux32:
GOOS=linux GOARCH=386 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth32 ${BUILD_FILES} && cd bin && sha256sum glauth32 > glauth32.sha256
linux64:
GOOS=linux GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth64 ${BUILD_FILES} && cd bin && sha256sum glauth64 > glauth64.sha256
linuxarm32:
GOOS=linux GOARCH=arm go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-arm32 ${BUILD_FILES} && cd bin && sha256sum glauth-arm32 > glauth-arm32.sha256
linuxarm64:
GOOS=linux GOARCH=arm64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-arm64 ${BUILD_FILES} && cd bin && sha256sum glauth-arm64 > glauth-arm64.sha256
darwin64:
GOOS=darwin GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauthOSX ${BUILD_FILES} && cd bin && sha256sum glauthOSX > glauthOSX.sha256
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 ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win32 ${BUILD_FILES} && cd bin && sha256sum glauth-win32 > glauth-win32.sha256
win64:
GOOS=windows GOARCH=amd64 go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/glauth-win64 ${BUILD_FILES} && cd bin && sha256sum glauth-win64 > glauth-win64.sha256
verify:
cd bin && sha256sum *.sha256 -c && cd ../;
+11 -208
View File
@@ -7,9 +7,6 @@ Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ co
![GitHub all releases](https://img.shields.io/github/downloads/glauth/glauth/total)
![Docker pulls](https://badgen.net/docker/pulls/glauth/glauth)
![Travis (.com) branch](https://img.shields.io/travis/com/glauth/glauth/dev)
![Docker Automated build](https://img.shields.io/docker/automated/glauth/glauth)
![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)
@@ -30,7 +27,7 @@ Use it to centralize account management across your Linux servers, your OSX mach
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!*
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).
2. Download the [example config file](https://github.com/glauth/glauth/blob/master/v2/sample-simple.cfg).
3. Start the GLAuth server, referencing the path to the desired config file with `-c`.
- `./glauth64 -c sample-simple.cfg`
4. Test with traditional LDAP tools
@@ -40,15 +37,15 @@ This quickstart is a great way to try out GLAuth in a non-production environment
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
### Documentation
*make fast* - run build for only linux 64 bit
<h4 align="center">:point_right: The latest version of GLauth's documentation is available at https://glauth.github.io/ :point_left:</h4>
*make run* - wrapper for the 'go run' command, setting up the needed tooling
<hr>
*make plugins* - build additional (SQL) plugin backends
### Quickstart
*make test* - run the integration test on linux64 binary
Get started in three short [steps](https://glauth.github.io/docs/quickstart.html)
### Usage:
```
@@ -97,214 +94,20 @@ Here's a sample config wth hardcoded users and groups:
name = "superheros"
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
```
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/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
* uidnumber
* The user's unix user id
* sshPublicKey
* Specify an array of public keys
### Optional Fields
* otherGroups
* Array of IDs of groups the user is a member of.
* Example: [5501, 5002]
* default = blank
* givenname
* First name
* Example: John
* default = blank
* sn
* Last name
* Example: Doe
* default = blank
* disabled
* Specify if account is active.
* Set to 'true' (without quotes) to make the LDAP entry add 'AccountStatus = inactive'
* default = false (active)
* mail
* Specify an email
* example: jdoe@example.com
* default = blank
* loginshell
* Specify a different login shell for the user
* Example: /bin/sh, or /sbin/nologin
* default = /bin/bash
* homedirectory
* Specify an overridden home directory for the user
* Example: /home/itadmin
* default = /home/[username]
* otpsecret
* Specify OTP secret used to validate OTP passcode
* Example: 3hnvnk4ycv44glzigd6s25j4dougs3rk
* default = blank
* 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.
#### 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 `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.
![Yubikey OTP](https://developers.yubico.com/OTP/otp_details.png)
When a user has been configured with either one of the OTP options, the OTP authentication is required for the user. If both are configured, either one will work.
More configuration options are documented [here](https://glauth.github.io/docs/file.html) and in this [sample file](https://github.com/glauth/glauth/blob/master/v2/sample-simple.cfg)
### Backends:
For advanced users, GLAuth supports pluggable backends. Currently, it can use a local file, S3 or an existing LDAP infrastructure. In the future, we hope to have backends that support Mongo, SQL, and other datastores.
For advanced users, GLAuth supports pluggable backends. Currently, it can use a local file, S3 or an existing LDAP infrastructure. Through the use of optional plugins, you can connect SQL databases, PAM, and other datastores.
```toml
[backend]
datastore = "ldap"
servers = [ "ldaps://server1:636", "ldaps://server2:636" ]
```
### Production:
Any of the architectures above will work for production. Just remember:
* Always use legit SSL certs for production!
# Other Architectures
A small note about other architectures: while I expect the code is, for the most part, system-independent, there is not a good (and free) CI system which can be easily used to continuously test releases on ARM, BSD, Linux-32bit, and Windows. As such, all of the non-linux-64bit packages are provided as is. The extent of testing on these packages consists solely of cross-compiling for these architectures from a linux 64 bit system.
We will accept PRs which fix bugs on these platforms, but be aware these binaries will not be tested regularly, and instead are provided for the convenience of those who feel comfortable with this.
### Building:
You'll need go-bindata to build GLAuth. Then use the Makefile.
```unix
go get github.com/jteeuwen/go-bindata/...
make all
```
# 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
# Testing
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.
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.
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
[![Stargazers over time](https://starchart.cc/glauth/glauth.svg)](https://starchart.cc/glauth/glauth)
-283
View File
@@ -1,283 +0,0 @@
// 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, "/")...)...)
}
-511
View File
@@ -1,511 +0,0 @@
package main
import (
"fmt"
"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
var LastGitTag string
var BuildTime string
var GitCommit string
var GitClean string
var GitBranch string
var GitTagIsCommit string
const programName = "glauth"
var usage = `glauth: securely expose your LDAP for external auth
Usage:
glauth [options] -c <file|s3 url>
glauth -h --help
glauth --version
Options:
-c, --config <file> Config file.
-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.
`
var (
log logr.Logger
args map[string]interface{}
stderr *logging.LogBackend
yubiAuth *yubigo.YubiAuth
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
// --version flag at runtime.
func getVersionString() string {
var versionstr string
versionstr = "GLauth"
// Notate the git context of the build
switch {
// If a release, use the tag
case GitClean == "1" && GitTagIsCommit == "1":
versionstr += " " + LastGitTag + "\n\n"
// If this branch had a tag before, mention the branch and the tag to give a rough idea of the base version
case len(GitBranch) > 1 && len(LastGitTag) > 1:
versionstr += "\nNon-release build from branch " + GitBranch + ", based on tag " + LastGitTag + "\n\n"
// If no previous tag specified, just mention the branch
case len(GitBranch) > 1:
versionstr += "\nNon-release build from branch " + GitBranch + "\n\n"
// Fallback message, if all else fails
default:
versionstr += "\nNon-release build\n\n"
}
// Include build time
if len(BuildTime) > 1 {
versionstr += "Build time: " + BuildTime + "\n"
}
// Add commit hash
if GitClean == "1" && len(GitCommit) > 1 {
versionstr += "Commit: " + GitCommit + "\n"
}
return versionstr
}
func main() {
stderr = initLogging()
log.V(6).Info("AP start")
if err := parseArgs(); err != nil {
log.Error(err, "Could not parse command-line arguments")
os.Exit(1)
}
if err := doConfig(); err != nil {
log.Error(err, "Configuration file error")
os.Exit(1)
}
startService()
}
func startService() {
// stats
stats.General.Set("version", stats.Stringer(LastGitTag))
// 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(configFileLocation, "s3://") {
if _, present := aws.Regions[args["-r"].(string)]; present == false {
return &cfg, fmt.Errorf("Invalid AWS region: %s", args["-r"])
}
region := aws.Regions[args["-r"].(string)]
auth, err := aws.EnvAuth()
if err != nil {
if args["-K"] == nil || args["-S"] == nil {
return &cfg, fmt.Errorf("AWS credentials not found: must use -K and -S flags, or set these env vars:\n\texport AWS_ACCESS_KEY_ID=\"AAA...\"\n\texport AWS_SECRET_ACCESS_KEY=\"BBBB...\"\n")
}
auth = aws.Auth{
AccessKey: args["-K"].(string),
SecretKey: args["-S"].(string),
}
}
// parse S3 url
s3url := strings.TrimPrefix(configFileLocation, "s3://")
parts := strings.SplitN(s3url, "/", 2)
if len(parts) != 2 {
return &cfg, fmt.Errorf("Invalid S3 URL: %s", s3url)
}
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
}
if _, err := toml.Decode(string(tomlData), &cfg); err != nil {
return &cfg, err
}
} else { // local config file
if _, err := toml.DecodeFile(configFileLocation, &cfg); err != nil {
return &cfg, err
}
}
// 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)
}
}
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))
return logBackend
}
// enableSyslog turns on syslog and turns off color
func enableSyslog(stderrBackend *logging.LogBackend) {
format := "%{time:15:04:05.000000} %{shortfunc} ▶ %{level:.4s} %{id:03x} %{message}"
logging.SetFormatter(logging.MustStringFormatter(format))
syslogBackend, err := logging.NewSyslogBackend("")
if err != nil {
log.Error(err, "Could not create new syslog backend")
os.Exit(1)
}
logging.SetBackend(stderrBackend, syslogBackend)
log.V(6).Info("Syslog enabled")
}
-28
View File
@@ -1,28 +0,0 @@
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
@@ -1,88 +0,0 @@
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=
+3
View File
@@ -0,0 +1,3 @@
go 1.19
use ./v2
-70
View File
@@ -1,70 +0,0 @@
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
@@ -1,186 +0,0 @@
// 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
@@ -1,27 +0,0 @@
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
}
}
-461
View File
@@ -1,461 +0,0 @@
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]
}
-25
View File
@@ -1,25 +0,0 @@
#####################
# 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
@@ -1,138 +0,0 @@
# 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.
-56
View File
@@ -1,56 +0,0 @@
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
@@ -1,56 +0,0 @@
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
@@ -1,82 +0,0 @@
#################
# 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
@@ -1,45 +0,0 @@
#################
# 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
@@ -1,56 +0,0 @@
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()
}
-73
View File
@@ -1,73 +0,0 @@
#################
# glauth.conf
#################
# General configuration.
debug = true
#syslog = true
#################
[ldap]
enabled = true
listen = "localhost:389"
[ldaps]
enabled = false
listen = "0.0.0.0:636"
cert = "cert.pem"
key = "key.pem"
#################
# The backend section controls the data store.
[backend]
datastore = "config"
baseDN = "dc=glauth,dc=com"
#################
# The users section contains a hardcoded list of valid users.
# to create a passSHA256: echo -n "mysecret" | openssl dgst -sha256
[[users]]
name = "hackers"
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
# This user record shows all of the possible fields available
[[users]]
name = "johndoe"
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
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"]
[[users]]
name = "serviceuser"
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
#################
# The groups section contains a hardcoded list of valid users.
[[groups]]
name = "superheros"
gidnumber = 5501
[[groups]]
name = "svcaccts"
gidnumber = 5502
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = false # enable TLS for production!!
listen = "localhost:5555"
cert = "cert.pem"
key = "key.pem"
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
img="localhost/glauth-dev"
sudo docker rmi -f "$img"
sudo docker build -f Dockerfile -t "$img" .
sudo docker run -P "$img"
-105
View File
@@ -1,105 +0,0 @@
dn: cn=superheros,ou=groups,dc=glauth,dc=com
cn: superheros
uid: superheros
description: superheros
gidNumber: 5501
objectClass: posixGroup
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
memberUid: alexdoe
memberUid: hackers
memberUid: jackdoe
memberUid: jamesdoe
memberUid: johndoe
memberUid: sarahdoe
dn: cn=svcaccts,ou=groups,dc=glauth,dc=com
cn: svcaccts
uid: svcaccts
description: svcaccts
gidNumber: 5502
objectClass: posixGroup
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
memberUid: serviceuser
dn: cn=vpnaccess,ou=groups,dc=glauth,dc=com
cn: vpnaccess
uid: vpnaccess
description: vpnaccess
gidNumber: 5503
objectClass: posixGroup
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
memberUid: alexdoe
memberUid: hackers
memberUid: jackdoe
memberUid: jamesdoe
memberUid: johndoe
memberUid: sarahdoe
dn: cn=allaccs,ou=groups,dc=glauth,dc=com
cn: allaccs
uid: allaccs
description: allaccs
gidNumber: 5504
objectClass: posixGroup
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=hackers,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=johndoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
memberUid: alexdoe
memberUid: hackers
memberUid: jackdoe
memberUid: jamesdoe
memberUid: johndoe
memberUid: sarahdoe
memberUid: serviceuser
dn: cn=mailadmin,ou=groups,dc=glauth,dc=com
cn: mailadmin
uid: mailadmin
description: mailadmin
gidNumber: 5505
objectClass: posixGroup
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
memberUid: alexdoe
memberUid: jackdoe
memberUid: jamesdoe
memberUid: sarahdoe
dn: cn=webmail,ou=groups,dc=glauth,dc=com
cn: webmail
uid: webmail
description: webmail
gidNumber: 5506
objectClass: posixGroup
dn: cn=fulltime,ou=groups,dc=glauth,dc=com
cn: fulltime
uid: fulltime
description: fulltime
gidNumber: 5507
objectClass: posixGroup
uniqueMember: cn=alexdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jackdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=jamesdoe,ou=superheros,dc=glauth,dc=com
uniqueMember: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
memberUid: alexdoe
memberUid: jackdoe
memberUid: jamesdoe
memberUid: sarahdoe
+22
View File
@@ -0,0 +1,22 @@
# Changelog
## [2.3.0](https://github.com/glauth/glauth/compare/v2.2.0...v2.3.0) (2023-10-02)
### ⚠ BREAKING CHANGES
* **plugins:** Rename the groups table to ldapgroups ([#326](https://github.com/glauth/glauth/issues/326))
### Features
* Update migration code to support table names ([#339](https://github.com/glauth/glauth/issues/339)) ([349431c](https://github.com/glauth/glauth/commit/349431c6caa0388d17ab987621eb9be5f019155e))
### Bug Fixes
* **plugins:** Rename the groups table to ldapgroups ([#326](https://github.com/glauth/glauth/issues/326)) ([675b236](https://github.com/glauth/glauth/commit/675b236328a21a65daa7876a1a3c6900b85b1964))
### Miscellaneous Chores
* release 2.3.0 ([0c11325](https://github.com/glauth/glauth/commit/0c11325a2482d5067c805a4c7ed948a5e337b8f8))
+228
View File
@@ -0,0 +1,228 @@
.DEFAULT_GOAL := help
VERSION=$(shell bin/linuxamd64/glauth --version)
GIT_COMMIT=$(shell git rev-list -1 HEAD )
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)
# this=1 if the current commit is the tagged commit (ie, if this is a release build)
GIT_IS_TAG_COMMIT=$(shell git describe --abbrev=0 --tags > /dev/null 2> /dev/null && echo "1" || echo "0")
# Used when a tag isn't available
GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
# Build variables
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=.
TRIM_FLAGS>?=-trimpath
# Targets
MAIN_TARGETS=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64,windows/amd64,windows/386
PLUGIN_TARGETS=linux/amd64,linux/386,linux/arm64,linux/arm-7,darwin/amd64,darwin/arm64
# For release process
GO_RELEASE_V=$(shell go version | { read _ _ v _; echo $${v#go}; })
# Build
GOOS?=linux
GOARCH?=amd64
# Plugins
-include pkg/plugins/*/Makefile
#####################
# High level commands
#####################
#help: @ List available tasks on this project
help:
@grep -E '[a-zA-Z\.\-]+:.*?@ .*$$' $(MAKEFILE_LIST)| tr -d '#' | sed -E 's/Makefile.//' | awk 'BEGIN {FS = ":.*?@ "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
#run: @ build and run - used for development
run: setup devrun
#test: @ runs the integration test on linuxamd64 (eventually allow the binary to be set)
test: runtest
#all: @ run build process for all binaries
all: setup binaries verify
#fast: @ run build process for only linuxamd64
fast: setup linuxamd64
#binaries: @ list of binary formats to build
binaries: linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64
#setup: @ setup commands to always run
setup: getdeps format
#####################
# Subcommands
#####################
# Run integration test
runtest:
./scripts/ci/integration-test.sh cleanup
# Get all dependencies
getdeps:
go get -d ./...
updatetest:
./scripts/ci/integration-test.sh
format:
go fmt
devrun:
go run ${BUILD_FILES} -c sample-simple.cfg
mkbindir:
@echo "create directory bin/$(GOOS)$(GOARCH)"
@mkdir -p bin/$(GOOS)$(GOARCH)
.PHONY: mkbindir
build: mkbindir
@go build ${TRIM_FLAGS} -ldflags "${BUILD_VARS}" -o bin/$(GOOS)$(GOARCH)/glauth -buildvcs .
$(MAKE) sha256
.PHONY: build
sha256:
@sha256sum bin/$(GOOS)$(GOARCH)/glauth > bin/$(GOOS)$(GOARCH)/glauth.sha256
.PHONY: sha256
linux386:
GOOS=linux GOARCH=386 $(MAKE) build
linuxamd64:
GOOS=linux GOARCH=amd64 $(MAKE) build
linuxarm:
GOOS=linux GOARCH=arm $(MAKE) build
linuxarm64:
GOOS=linux GOARCH=arm64 $(MAKE) build
darwinamd64:
GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 $(MAKE) build
darwinarm64:
GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 $(MAKE) build
win386:
GOOS=windows GOARCH=386 $(MAKE) build
winamd64:
GOOS=windows GOARCH=amd64 $(MAKE) build
verify:
@for binary in linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64; do cd bin/$$binary && sha256sum glauth.sha256 -c && cd ../..; done
pull-plugin:
@git submodule add $U $M && \
(for pkg in $$(cat $$M/go.mod | awk '/^require [a-z]/{print $$2} /^\)/{rblock=0} rblock{if($$4!="indirect" && $$1!~"glauth"){print $$1}} /^require \(/{rblock=1}'); do if [ "$$(grep $$pkg go.mod &>/dev/null)" = "" ]; then go get $$pkg; fi; done)
#pull-base-plugins: @ pull plugins for local building
pull-base-plugins:
@U=https://github.com/glauth/glauth-sqlite M=pkg/plugins/glauth-sqlite make pull-plugin && \
U=https://github.com/glauth/glauth-mysql M=pkg/plugins/glauth-mysql make pull-plugin && \
U=https://github.com/glauth/glauth-postgres M=pkg/plugins/glauth-postgres make pull-plugin && \
U=https://github.com/glauth/glauth-pam M=pkg/plugins/glauth-pam make pull-plugin
forget-plugin:
rm -rf $M ../.git/modules/v2/$M && \
git config --remove-section submodule.v2/$M && \
git rm --cache $M && \
go mod tidy
#forget-plugin: @ remove plugins, restoring original file structure
forget-plugins:
for pkg in pkg/plugins/*/go.mod; do\
P=$$(echo $$pkg | cut -d'/' -f 3) M=$$(dirname $$pkg) make forget-plugin;\
done
#releasemain: @ build main binaries for distribution
releasemain:
@xgo -v -ldflags="${BUILD_VARS}" -trimpath -go ${GO_RELEASE_V} -out glauth -dest bin -buildvcs=false --targets="${MAIN_TARGETS}" .
releaseplugin:
@xgo -v -ldflags="${BUILD_VARS}" -trimpath -go ${GO_RELEASE_V} -out $P -dest bin -buildvcs=false -buildmode=plugin --targets="${PLUGIN_TARGETS}" --pkg $M/$P.go . && \
(cd bin && for lib in $$(ls $$P-*); do sudo mv $$lib $$lib.so; done)
#releaseplugins: @ build base plugins for distribution
releaseplugins:
@for pkg in pkg/plugins/*/go.mod; do\
M=pkg/plugins/$$(echo $$pkg | cut -d'/' -f 3) && P=$$(echo $$M | grep -oP 'glauth-\K\w+') && M=$$M P=$$P make releaseplugin;\
done
releasedockermain:
$(if $(TAG),,$(error Must set TAG))
$(if $(REPO),,$(error Must set REPO - glauth or other))
@mkdir -p docker/assets/linux/amd64 docker/assets/linux/arm64 docker/assets/linux/arm/v7 && \
cp -f bin/glauth-linux-amd64 docker/assets/linux/amd64/glauth && \
cp -f bin/glauth-linux-arm64 docker/assets/linux/arm64/glauth && \
cp -f bin/glauth-linux-arm-7 docker/assets/linux/arm/v7/glauth && \
docker buildx build --tag $$REPO/glauth:$$TAG -t $$REPO/glauth:latest -f docker/Dockerfile-standalone --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker
releasedockerplugins:
$(if $(TAG),,$(error Must set TAG))
$(if $(REPO),,$(error Must set REPO - glauth or other))
@mkdir -p docker/assets/linux/amd64 docker/assets/linux/arm64 docker/assets/linux/arm/v7 && \
cp -f bin/sqlite-linux-amd64.so docker/assets/linux/amd64/sqlite.so && \
cp -f bin/sqlite-linux-arm64.so docker/assets/linux/arm64/sqlite.so && \
cp -f bin/sqlite-linux-arm-7.so docker/assets/linux/arm/v7/sqlite.so && \
cp -f bin/mysql-linux-amd64.so docker/assets/linux/amd64/mysql.so && \
cp -f bin/mysql-linux-arm64.so docker/assets/linux/arm64/mysql.so && \
cp -f bin/mysql-linux-arm-7.so docker/assets/linux/arm/v7/mysql.so && \
cp -f bin/postgres-linux-amd64.so docker/assets/linux/amd64/postgres.so && \
cp -f bin/postgres-linux-arm64.so docker/assets/linux/arm64/postgres.so && \
cp -f bin/postgres-linux-arm-7.so docker/assets/linux/arm/v7/postgres.so && \
docker buildx build --tag $$REPO/glauth-plugins:$$TAG -t $$REPO/glauth-plugins:latest -f docker/Dockerfile-plugins --platform linux/amd64,linux/arm64,linux/arm/v7 --push docker
releasedocker: releasedockermain releasedockerplugins
#testdocker: @ run integration test using docker
testdocker:
$(if $(REPO),,$(error Must set REPO - glauth or other))
@echo "==> Cleaning up any existing image to be on the safe side..." && \
(for image in $$(docker image ls -q "$$REPO/glauth*"); do \
for container in $$(docker container ls -a -q -f ancestor=$$image); do \
docker stop $$container; \
while [ "$$(docker container ls -q -f ancestor=$$image)" != "" ]; do sleep 1; done; \
docker rm $$container; \
done; \
docker rmi $$image; \
done) && \
echo "==> Running glauth main container..." && \
docker run -d --name glauth-test -p 3893:3893 $$REPO/glauth:latest && \
sleep 5 && \
if [ "$$(ldapsearch -LLL -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bdc=glauth,dc=com cn=hackers | grep posixAccount)" != "" ]; then \
echo "Checked: Glauth is responding properly to ldapsearch query."; \
else \
echo "glauth check did not pass. Aborting."; \
exit 1; \
fi && \
echo "==> Stopping glauth main container..." && \
docker stop glauth-test && \
while [ "$$(docker ps -q -f name=glauth-test)" != "" ]; do sleep 1; done; \
docker rm glauth-test && \
echo "==> Running glauth plugins container..." && \
docker run -d --name glauth-test -p 3893:3893 $$REPO/glauth-plugins:latest && \
sleep 5 && \
if [ "$$(ldapsearch -LLL -H ldap://localhost:3893 -D cn=serviceuser,ou=svcaccts,dc=glauth,dc=com -w mysecret -x -bdc=glauth,dc=com cn=hackers | grep posixAccount)" != "" ]; then \
echo "Checked: Glauth is responding properly to ldapsearch query."; \
else \
echo "glauth check did not pass. Aborting."; \
exit 1; \
fi && \
echo "==> Stopping glauth plugins container..." && \
docker stop glauth-test && \
while [ "$$(docker ps -q -f name=glauth-test)" != "" ]; do sleep 1; done; \
docker rm glauth-test && \
echo "==> Testing complete."
.PHONY: all run test fast binaries setup getdeps runtest updatetest format devrun linux386 linuxamd64 linuxarm linuxarm64 darwinamd64 darwinarm64 win386 winamd64 verify pull-plugin pull-base-plugins forget-plugin forget-plugins releasemain releaseplugin release releaseplugins releasedockermain releaserdockerplugins releasedocker testdocker
+35
View File
@@ -0,0 +1,35 @@
#################
# Build Step
#################
FROM alpine:latest as build
RUN apk add --no-cache git make busybox-static dumb-init
#################
# Run Step
#################
FROM gcr.io/distroless/base-debian11 as run
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
ARG TARGETPLATFORM
# Copy docker specific scripts from build container
COPY assets/start-plugins.sh /app/docker/start-plugins.sh
COPY assets/default-config-plugins.cfg /app/docker/default-config-plugins.cfg
COPY assets/gl.db /app/docker/gl.db
COPY assets/$TARGETPLATFORM/glauth /app/glauth
COPY assets/$TARGETPLATFORM/*.so /app/
# Just what we need
COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init
COPY --from=build /bin/busybox.static /bin/sh
COPY --from=build /bin/busybox.static /bin/ln
COPY --from=build /bin/busybox.static /bin/rm
RUN ln /bin/sh /usr/bin/cp && ln /bin/sh /usr/bin/mkdir && rm /bin/ln /bin/rm
# Expose web and LDAP ports
EXPOSE 389 636 5555
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/bin/sh", "/app/docker/start-plugins.sh"]
+33
View File
@@ -0,0 +1,33 @@
#################
# Build Step
#################
FROM alpine:latest as build
RUN apk add --no-cache git make busybox-static dumb-init
#################
# Run Step
#################
FROM gcr.io/distroless/base-debian11 as run
LABEL maintainers="Ben Yanke <ben@benyanke.com>, Jörn Friedrich Dreyer <jfd@butonic.de>, Chris F Ravenscroft <chris@voilaweb.com>"
ARG TARGETPLATFORM
# Copy docker specific scripts from build container
COPY assets/start-standalone.sh /app/docker/start-standalone.sh
COPY assets/default-config-standalone.cfg /app/docker/default-config-standalone.cfg
COPY assets/$TARGETPLATFORM/glauth /app/glauth
# Just what we need
COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init
COPY --from=build /bin/busybox.static /bin/sh
COPY --from=build /bin/busybox.static /bin/ln
COPY --from=build /bin/busybox.static /bin/rm
RUN ln /bin/sh /usr/bin/cp && ln /bin/sh /usr/bin/mkdir && rm /bin/ln /bin/rm
# Expose web and LDAP ports
EXPOSE 389 636 5555
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/bin/sh", "/app/docker/start-standalone.sh"]
@@ -0,0 +1,38 @@
#################
# glauth.conf
#################
# General configuration.
debug = true
#syslog = true
#################
[ldap]
enabled = true
listen = "0.0.0.0:3893"
[ldaps]
enabled = false
listen = "0.0.0.0:3894"
cert = "cert.pem"
key = "key.pem"
#################
# The backend section controls the data store.
[backend]
datastore = "plugin"
# If "plugin," uncomment the line below
plugin = "/app/sqlite.so"
pluginhandler = "NewSQLiteHandler"
baseDN = "dc=glauth,dc=com"
database = "/app/config/gl.db"
#################
# Enable and configure the optional REST API here.
[api]
enabled = true
tls = false # enable TLS for production!!
listen = "localhost:5555"
cert = "cert.pem"
key = "key.pem"
@@ -0,0 +1,150 @@
#################
# 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.
[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]
# Ignore all capabilities restrictions, for instance allowing every user to perform a search
IgnoreCapabilities = false
# 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"
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
# This user record shows all of the possible fields available
[[users]]
name = "johndoe"
givenname="John"
sn="Doe"
mail = "jdoe@example.com"
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"
mail = "serviceuser@example.com"
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
[[users.capabilities]]
action = "search"
object = "*"
# Test user showing 2 factor auth authentication
[[users]]
name = "otpuser"
uidnumber = 5004
primarygroup = 5501
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
yubikey = "vvjrcfalhlaa"
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
[[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"
gidnumber = 5501
[[groups]]
name = "svcaccts"
gidnumber = 5502
[[groups]]
name = "vpn"
gidnumber = 5503
includegroups = [ 5501 ]
#################
# 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"
Binary file not shown.
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# Copy config file if it doesn't exist so that the app can start
if [ ! -f /app/config/config.cfg ] ; then
echo "Config file not found at /app/config/config.cfg"
echo "Copying example configuration file to run."
mkdir -p /app/config
cp /app/docker/default-config-plugins.cfg /app/config/config.cfg || exit 1
# This file will be ignored when switching away from the default (sqlite)
cp /app/docker/gl.db /app/config/gl.db || 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
cd /app
/app/glauth -c /app/config/config.cfg
echo ""
echo "GLauth has exited."
echo "Exiting."
@@ -4,7 +4,8 @@
if [ ! -f /app/config/config.cfg ] ; then
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
mkdir -p /app/config
cp /app/docker/default-config-standalone.cfg /app/config/config.cfg || exit 1
fi
+788
View File
@@ -0,0 +1,788 @@
package main
import (
"bytes"
"fmt"
"github.com/GeertJohan/yubigo"
"github.com/arl/statsviz"
docopt "github.com/docopt/docopt-go"
"github.com/fsnotify/fsnotify"
"github.com/glauth/glauth/v2/pkg/config"
"github.com/glauth/glauth/v2/pkg/frontend"
"github.com/glauth/glauth/v2/pkg/logging"
"github.com/glauth/glauth/v2/pkg/server"
"github.com/glauth/glauth/v2/pkg/stats"
"github.com/hydronica/toml"
"github.com/jinzhu/copier"
"github.com/rs/zerolog"
"gopkg.in/amz.v3/aws"
"gopkg.in/amz.v3/s3"
"net/http"
"os"
"path/filepath"
"reflect"
"strings"
"time"
)
// Set with buildtime vars
var LastGitTag string
var BuildTime string
var GitCommit string
var GitClean string
var GitBranch string
var GitTagIsCommit string
const programName = "glauth"
var usage = `glauth: securely expose your LDAP for external auth
Usage:
glauth [options] -c <file|s3 url>
glauth -h --help
glauth --version
Options:
-c, --config <file> Config file.
-K <aws_key_id> AWS Key ID.
-S <aws_secret_key> AWS Secret Key.
-r <aws_region> AWS Region [default: us-east-1].
--aws_endpoint_url <url> Custom S3 endpoint.
--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.
--check-config Check configuration file and exit.
-h, --help Show this screen.
--version Show version.
`
var (
log zerolog.Logger
args map[string]interface{}
yubiAuth *yubigo.YubiAuth
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
// --version flag at runtime.
func getVersionString() string {
var versionstr string
versionstr = "GLauth"
// Notate the git context of the build
switch {
// If a release, use the tag
case GitClean == "1" && GitTagIsCommit == "1":
versionstr += " " + LastGitTag + "\n\n"
// If this branch had a tag before, mention the branch and the tag to give a rough idea of the base version
case len(GitBranch) > 1 && len(LastGitTag) > 1:
versionstr += "\nNon-release build from branch " + GitBranch + ", based on tag " + LastGitTag + "\n\n"
// If no previous tag specified, just mention the branch
case len(GitBranch) > 1:
versionstr += "\nNon-release build from branch " + GitBranch + "\n\n"
// Fallback message, if all else fails
default:
versionstr += "\nNon-release build\n\n"
}
// Include build time
if len(BuildTime) > 1 {
versionstr += "Build time: " + BuildTime + "\n"
}
// Add commit hash
if GitClean == "1" && len(GitCommit) > 1 {
versionstr += "Commit: " + GitCommit + "\n"
}
return versionstr
}
func main() {
if err := parseArgs(); err != nil {
fmt.Println("Could not parse command-line arguments")
fmt.Println(err)
os.Exit(1)
}
checkConfig := false
if cc, ok := args["--check-config"]; ok {
if cc == true {
checkConfig = true
}
}
if err := doConfig(checkConfig); err != nil {
fmt.Println("Configuration file error")
fmt.Println(err)
os.Exit(1)
}
if checkConfig {
fmt.Println("Config file seems ok (but I am not checking much at this time)")
return
}
log.Info().Msg("AP start")
startService()
}
func startService() {
// stats
stats.General.Set("version", stats.Stringer(LastGitTag))
// web API
if activeConfig.API.Enabled {
log.Info().Msg("Web API enabled")
if activeConfig.API.Internals {
statsviz.Register(
http.DefaultServeMux,
statsviz.Root("/internals"),
statsviz.SendFrequency(1000*time.Millisecond),
)
}
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(err).Msg("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(err).Msg("could not start LDAP server")
os.Exit(1)
}
} else {
go func() {
if err := s.ListenAndServe(); err != nil {
log.Error().Err(err).Msg("could not start LDAP server")
os.Exit(1)
}
}()
}
}
if activeConfig.LDAPS.Enabled {
// Always block here
if err := s.ListenAndServeTLS(); err != nil {
log.Error().Err(err).Msg("could not start LDAPS server")
os.Exit(1)
}
}
log.Info().Msg("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(err).Msg("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.Info().Str("e", event.Op.String()).Msg("watcher got event")
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(err).Msg("watcher error")
case <-ticker.C:
// wakeup, try finding removed config
}
if _, err := os.Stat(configFileLocation); !os.IsNotExist(err) && (isRemoved || isChanged) {
if isRemoved {
log.Info().Str("file", configFileLocation).Msg("rewatching config")
watcher.Add(configFileLocation) // overwrite
isChanged, isRemoved = true, false
}
if isChanged {
if err := doConfig(false); err != nil {
log.Info().Err(err).Msg("Could not reload config. Holding on to old config")
} else {
log.Info().Msg("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(configFileLocation, "s3://") {
region, present := aws.Regions[args["-r"].(string)]
if present == false {
return &cfg, fmt.Errorf("invalid AWS region: %s", args["-r"])
}
if args["--aws_endpoint_url"] != nil {
region = aws.Region{
Name: "User defined",
S3Endpoint: args["--aws_endpoint_url"].(string),
}
present = true
}
auth, err := aws.EnvAuth()
if err != nil {
if args["-K"] == nil || args["-S"] == nil {
return &cfg, fmt.Errorf("AWS credentials not found: must use -K and -S flags, or set these env vars:\n\texport AWS_ACCESS_KEY_ID=\"AAA...\"\n\texport AWS_SECRET_ACCESS_KEY=\"BBBB...\"\n")
}
auth = aws.Auth{
AccessKey: args["-K"].(string),
SecretKey: args["-S"].(string),
}
}
// parse S3 url
s3url := strings.TrimPrefix(configFileLocation, "s3://")
parts := strings.SplitN(s3url, "/", 2)
if len(parts) != 2 {
return &cfg, fmt.Errorf("invalid S3 URL: %s", s3url)
}
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
}
if _, err := toml.Decode(string(tomlData), &cfg); err != nil {
return &cfg, err
}
} else { // local config file
fInfo, err := os.Stat(configFileLocation)
if err != nil {
return &cfg, fmt.Errorf("non-existent config path: %s", configFileLocation)
}
var md toml.MetaData
if fInfo.IsDir() { // multiple files in a directory
rawCfgStruct := make(map[string]interface{})
// To keep things simple, we are not going to use the default values built in Cfg
// so far (LDAP.Enabled, LDAPS.Enabled, etc) so do not forget to specify them!
/*
sourcebuf := new(bytes.Buffer)
err = toml.NewEncoder(sourcebuf).Encode(cfg)
if err != nil {
return &cfg, err
}
var initialRawCfgStruct interface{}
if err := toml.Unmarshal(sourcebuf.Bytes(), &initialRawCfgStruct); err != nil {
return &cfg, err
}
if err = mergeConfigs(&rawCfgStruct, initialRawCfgStruct); err != nil {
return &cfg, err
}
*/
files, _ := os.ReadDir(configFileLocation)
for _, f := range files {
canonicalName := filepath.Join(configFileLocation, f.Name())
bs, _ := os.ReadFile(canonicalName)
var curRawCfgStruct interface{}
if err := toml.Unmarshal(bs, &curRawCfgStruct); err != nil {
return &cfg, err
}
if err = mergeConfigs(&rawCfgStruct, curRawCfgStruct); err != nil {
return &cfg, err
}
}
destbuf := new(bytes.Buffer)
err = toml.NewEncoder(destbuf).Encode(rawCfgStruct)
if err != nil {
return &cfg, err
}
fmt.Println(destbuf.String())
merged := config.Config{}
if md, err = toml.Decode(destbuf.String(), &merged); err != nil {
return &cfg, err
}
cfg = merged
} else {
md, err = toml.DecodeFile(configFileLocation, &cfg)
if err != nil {
return &cfg, err
}
}
switch users := md.Mappings()["users"].(type) {
case []map[string]interface{}:
for _, mduser := range users {
if mduser["customattributes"] != nil {
for idx, cfguser := range cfg.Users {
if cfguser.Name == mduser["name"].(string) {
switch attributes := mduser["customattributes"].(type) {
case []map[string]interface{}:
cfg.Users[idx].CustomAttrs = attributes[0]
case map[string]interface{}:
cfg.Users[idx].CustomAttrs = attributes
default:
log.Info().Interface("attributes", attributes).Msg("Unknown attribute structure in config file")
}
break
}
}
}
}
}
}
// 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)
}
}
// 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 mergeConfigs(config1 interface{}, config2 interface{}) error {
var merger func(int, string, interface{}, interface{}) error
merger = func(depth int, keyName string, cfg1 interface{}, cfg2 interface{}) error {
//fmt.Println(strings.Repeat(" ", depth), "Handling element: ", keyName, " for: ", cfg2)
switch element2 := cfg2.(type) {
case map[string]interface{}:
//fmt.Println(strings.Repeat(" ", depth), " - A map")
element2, ok := cfg2.(map[string]interface{})
if !ok {
return fmt.Errorf("config source: %s is not a map", keyName)
}
element1, ok := cfg1.(*map[string]interface{})
if !ok {
return fmt.Errorf("config dest: %s is not a map", keyName)
}
for k, _ := range element2 {
//fmt.Println(strings.Repeat(" ", depth), " - key: ", k)
_, ok := (*element1)[k]
if !ok {
(*element1)[k] = element2[k]
} else {
//fmt.Println(strings.Repeat(" ", depth), " - merging: ", element2[k])
asanarrayptr, ok := (*element1)[k].([]map[string]interface{})
if ok {
if err := merger(depth+1, k, &asanarrayptr, element2[k]); err != nil {
return err
}
(*element1)[k] = asanarrayptr
} else {
asamapptr, ok := (*element1)[k].(map[string]interface{})
if ok {
if err := merger(depth+1, k, &asamapptr, element2[k]); err != nil {
return err
}
(*element1)[k] = asamapptr
} else {
return fmt.Errorf("config dest: %s does not make a valid map/array ptr", keyName)
}
}
}
}
case []map[string]interface{}:
//fmt.Println(strings.Repeat(" ", depth), " - An array")
element2, ok := cfg2.([]map[string]interface{})
if !ok {
return fmt.Errorf("config source: %s is not a map array", keyName)
}
//fmt.Println(strings.Repeat(" ", depth), " - element2: ", element2)
element1, ok := cfg1.(*[]map[string]interface{})
if !ok {
return fmt.Errorf("config dest: %s is not a map array", keyName)
}
//fmt.Println(strings.Repeat(" ", depth), " - element1: ", element1)
for index, _ := range element2 {
*element1 = append(*element1, element2[index])
}
case string:
//fmt.Println(strings.Repeat(" ", depth), " - A string")
element2, ok := cfg2.(string)
if !ok {
return fmt.Errorf("config: %s is not a string", keyName)
}
case bool:
//fmt.Println(strings.Repeat(" ", depth), " - A boolean")
element2, ok := cfg2.(bool)
if !ok {
return fmt.Errorf("config: %s is not a boolean value", keyName)
}
case float64:
//fmt.Println(strings.Repeat(" ", depth), " - A float64")
element2, ok := cfg2.(float64)
if !ok {
return fmt.Errorf("config: %s is not a float64 value", keyName)
}
case nil:
//fmt.Println(strings.Repeat(" ", depth), " - Nil")
default:
log.Info().Str("type", reflect.TypeOf(element2).String()).Msg("Unknown element type found in configuration file. Ignoring.")
}
return nil
}
err := merger(0, "TOP", config1, config2)
if err != nil {
return err
}
return nil
}
func mergeConfigsO(config1 interface{}, config2 interface{}) (interface{}, error) {
var merger func(int, string, interface{}, interface{}) (interface{}, error)
merger = func(depth int, keyName string, cfg1 interface{}, cfg2 interface{}) (interface{}, error) {
var returnElement interface{}
fmt.Println(strings.Repeat(" ", depth), "Handling element: ", keyName, " for: ", cfg1)
switch element1 := cfg1.(type) {
case map[string]interface{}:
element2, ok := cfg2.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("config: %s is not a map", keyName)
}
for k, value2 := range element2 {
value1, ok := element1[k]
if !ok {
return nil, fmt.Errorf("config: %s not found in at least one config file", k)
}
merged, err := merger(depth+1, k, value1, value2)
if err != nil {
return merged, err
}
element1[k] = merged
}
returnElement = element1
case string:
element2, ok := cfg2.(string)
if !ok {
return nil, fmt.Errorf("config: %s is not a string", keyName)
}
if element2 != "" {
element1 = element2
}
returnElement = element1
case bool:
element2, ok := cfg2.(bool)
if !ok {
return nil, fmt.Errorf("config: %s is not a boolean value", keyName)
}
if element2 {
element1 = true
}
returnElement = element1
case float64:
element2, ok := cfg2.(float64)
if !ok {
return nil, fmt.Errorf("config: %s is not a float64 value", keyName)
}
if element2 != 0 {
element1 = element2
}
returnElement = element1
case nil:
if cfg2 == nil {
returnElement = nil
} else {
element2, ok := cfg2.(map[string]interface{})
if ok {
returnElement = element2
} else {
element3, ok := cfg2.([]interface{})
if ok {
returnElement = element3
} else {
log.Info().Msg("Unexpected interface type for an assignment. Ignoring.")
returnElement = element1
}
}
}
default:
log.Info().Str("type", reflect.TypeOf(element1).String()).Msg("Unknown element type found in configuration file. Ignoring.")
returnElement = element1
}
fmt.Println(strings.Repeat(" ", depth), "Done with element: ", keyName, " for: ", returnElement)
return returnElement, nil
}
/*
flattenedConfig1, _ := json.Marshal(config1)
var jsonConfig1 interface{}
//json.Unmarshal(flattenedConfig1, &jsonConfig1)
d1 := json.NewDecoder(bytes.NewReader(flattenedConfig1))
d1.UseNumber()
if err := d1.Decode(&jsonConfig1); err != nil {
return config1, err
}
flattenedConfig2, _ := json.Marshal(config2)
var jsonConfig2 interface{}
//json.Unmarshal(flattenedConfig2, &jsonConfig2)
//spew.Dump(jsonConfig2)
d2 := json.NewDecoder(bytes.NewReader(flattenedConfig2))
d2.UseNumber()
if err := d2.Decode(&jsonConfig2); err != nil {
return config1, err
}
*/
blocks, err := merger(0, "TOP", config1, config2)
if err != nil {
return config1, err
}
//
//
// TODO: return blocks... we will convert back to toml when we are done merging!
//
buf := new(bytes.Buffer)
err = toml.NewEncoder(buf).Encode(blocks)
if err != nil {
return config1, err
}
fmt.Println(buf.String())
merged := config.Config{}
if _, err := toml.Decode(buf.String(), &merged); err != nil {
return config1, err
}
return merged, 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.Info().Msg("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.Info().Msg(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.Info().Msg(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(checkConfig bool) 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 activeConfig
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
log = logging.InitLogging(activeConfig.Debug, activeConfig.Syslog, activeConfig.StructuredLog)
if !checkConfig {
if activeConfig.Debug {
log.Info().Msg("Debugging enabled")
}
if activeConfig.Syslog {
log.Info().Msg("Syslog enabled")
}
}
return nil
}
+30 -13
View File
@@ -18,8 +18,10 @@ type testEnv struct {
otpdn string
expectedinfo string
expectedaccount string
scopedaccount string
expectedfirstaccount string
expectedgroup string
checkemployeetype string
}
func TestIntegerStuff(t *testing.T) {
@@ -36,9 +38,11 @@ func TestIntegerStuff(t *testing.T) {
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",
expectedaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
scopedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
checkemployeetype: "cn=hackers",
})
stopSvc(svc)
})
@@ -57,9 +61,11 @@ func TestIntegerStuff(t *testing.T) {
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",
expectedaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
scopedaccount: "dn: cn=hackers,ou=superheros,dc=glauth,dc=com",
expectedfirstaccount: "dn: cn=hackers,ou=superheros,ou=users,dc=glauth,dc=com",
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
checkemployeetype: "",
})
stopSvc(svc)
})
@@ -80,8 +86,10 @@ func TestIntegerStuff(t *testing.T) {
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",
scopedaccount: "dn: cn=hackers,cn=superheros,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",
expectedgroup: "dn: ou=superheros,ou=users,dc=glauth,dc=com",
checkemployeetype: "",
})
stopSvc(svc)
})
@@ -138,14 +146,14 @@ func batteryOfTests(t *testing.T, svc *exec.Cmd, env testEnv) {
})
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)")
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(memberOf=ou=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))")
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", "(&(objectClass=*)(memberOf=ou=superheros,ou=groups,dc=glauth,dc=com))")
Convey("We should get a list starting with the 'hackers' user", func() {
So(out, ShouldEqual, env.expectedfirstaccount)
})
@@ -154,27 +162,36 @@ func batteryOfTests(t *testing.T, svc *exec.Cmd, env testEnv) {
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")
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret"+otpvalue, "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
Convey("We should find them in in the 'superheros' group", func() {
So(out, ShouldEqual, env.expectedaccount)
So(out, ShouldEqual, env.scopedaccount)
})
})
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")
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret", "-x", "-bou=superheros,dc=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")
out := doRunGetFirst(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.otpdn, "-w", "mysecret123456", "-x", "-bou=superheros,dc=glauth,dc=com", "cn=hackers")
Convey("We should get 'Invalid credentials(49)'", func() {
So(out, ShouldEqual, "exit status 49")
})
})
}
if env.checkemployeetype != "" {
Convey("When searching for the 'hacker' user", func() {
out := doRunGetSecond(RD, "ldapsearch", "-LLL", "-H", "ldap://localhost:3893", "-D", env.svcdn, "-w", "mysecret", "-x", "-bdc=glauth,dc=com", env.checkemployeetype, "employeetype")
Convey("Employee type should be 'Intern'", func() {
So(out, ShouldEqual, "employeetype: Intern")
})
})
}
}
// -----=============================================================================----
+44
View File
@@ -0,0 +1,44 @@
module github.com/glauth/glauth/v2
go 1.19
// Do not mistake /vendored for /vendor!
replace github.com/hydronica/toml => ./vendored/toml
require (
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
github.com/arl/statsviz v0.4.0
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/fsnotify/fsnotify v1.4.9
github.com/hydronica/toml v0.4.2
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
github.com/pquerna/otp v1.3.0
github.com/prometheus/client_golang v1.13.0
github.com/rs/zerolog v1.28.0
github.com/smartystreets/goconvey v1.6.4
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc
golang.org/x/crypto v0.1.0
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
golang.org/x/sys v0.6.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
+534
View File
@@ -0,0 +1,534 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
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/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/arl/statsviz v0.4.0 h1:pv8DWfti0TC8M2Baold2xGnCnir9jhqv1BO6UNKQmqY=
github.com/arl/statsviz v0.4.0/go.mod h1:+5inUy/dxy11x/KSmicG3ZrEEy0Yr81AFm3dn4QC04M=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
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/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
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-20210720162743-7f8d1e44eeba h1:DO8NFYdcRv1dnyAINJIBm6Bw2XibtLvQniNFGzf2W8E=
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba/go.mod h1:4S0XndRL8HNOaQBfdViJ2F/GPCgL524xlXRuXFH12/U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190213061140-3a22650c66bd/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
package assets
import "embed"
// Content is the embedded assets.
//
//go:embed *.tgz js/* css/* *.html
var Content embed.FS
+17 -12
View File
@@ -9,9 +9,9 @@
<title>GLAuth Status</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="assets/glauth.css">
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="assets/css/glauth.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,18 @@
<div id="statspanel" class="panel panel-default pull-right">
</div>
<h1>GLAuth</h1>
<p>This tool is in development - not quite ready for production!</p>
<div id="headerpanel"></div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6">
<h2>stats</h2>
<h2>frontend stats</h2>
<div id="stats" class="jumbotron">
</div>
</div>
<div class="col-xs-12 col-sm-6">
<h2>backend servers</h2>
<h2>backend (LDAP) servers</h2>
<div id="servers" class="jumbotron">
</div>
</div>
@@ -49,6 +48,12 @@
</div> <!-- /container -->
<script id="header-messages-template" type="text/x-handlebars-template">
{{#each header.messages}}
<span>{{{message}}}</span><br>
{{/each}}
</script>
<script id="statspanel-template" type="text/x-handlebars-template">
<div class="panel-body">
<span class="metric">proxy.version:</span> {{proxy.version}}<br>
@@ -87,10 +92,10 @@
<tbody>
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment-with-locales.min.js"></script>
<script src="assets/glauth.js"></script>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/handlebars.min.js"></script>
<script src="assets/js/moment-with-locales.min.js"></script>
<script src="assets/js/glauth.js"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -1,14 +1,32 @@
var interval = 2000;
var internalsEnabled = false;
$(function() {
updateMetrics();
//updateTail();
$.get("/internals", function() {
internalsEnabled = true;
})
.always(function() {
updateMetrics();
//updateTail();
})
});
var tailId = 0;
function updateMetrics() {
var metrics = $.getJSON( "/debug/vars", function(d) {
var template = Handlebars.compile( $("#header-messages-template").html() );
var messages = [];
if(internalsEnabled) {
messages.push({"message": "Also available: <a href='/internals'>Internals: heap, cache, objects, etc.</a>"});
}
var html = template({
"header": {
"messages": messages
}
});
$("#headerpanel").html(html);
var template = Handlebars.compile( $("#statspanel-template").html() );
var html = template(d);
$("#statspanel").html(html);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,18 +4,19 @@ 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
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
GroupWithSearchCapability string // For PamLinux backend only
AnonymousDSE bool // For Config and Database backends only
}
type Helper struct {
Enabled bool
@@ -45,19 +46,29 @@ type LDAPS struct {
type API struct {
Cert string
Enabled bool
Internals bool
Key string
Listen string
SecretToken string
TLS bool
}
type Behaviors struct {
IgnoreCapabilities bool
LimitFailedBinds bool
NumberOfFailedBinds int
PeriodOfFailedBinds time.Duration
BlockFailedBindsFor time.Duration
PruneSourceTableEvery time.Duration
PruneSourcesOlderThan time.Duration
LegacyVersion int
}
type Capability struct {
Action string
Object string
}
// UserAuthenticator authenticates a user via custom auth from a backend
type UserAuthenticator func(user *User, pw string) error
type User struct {
Name string
OtherGroups []int
@@ -65,7 +76,9 @@ type User struct {
PassBcrypt string
PassAppSHA256 []string
PassAppBcrypt []string
PassAppCustom UserAuthenticator
PrimaryGroup int
Capabilities []Capability
SSHKeys []string
OTPSecret string
Yubikey string
@@ -77,6 +90,7 @@ type User struct {
GivenName string
SN string
Homedir string
CustomAttrs map[string]interface{}
}
type Group struct {
Name string
@@ -91,6 +105,8 @@ type Config struct {
Helper Helper
Behaviors Behaviors
Debug bool
Syslog bool
StructuredLog bool
WatchConfig bool
YubikeyClientID string
YubikeySecret string
@@ -98,7 +114,6 @@ type Config struct {
LDAP LDAP
LDAPS LDAPS
Groups []Group
Syslog bool
Users []User
ConfigFile string
AwsAccessKeyId string
@@ -2,9 +2,9 @@ package frontend
import (
"context"
"github.com/rs/zerolog"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
"github.com/glauth/glauth/v2/pkg/config"
)
// Option defines a single option function.
@@ -12,7 +12,7 @@ type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger logr.Logger
Logger zerolog.Logger
Config *config.API
Context context.Context
}
@@ -29,7 +29,7 @@ func newOptions(opts ...Option) Options {
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
func Logger(val zerolog.Logger) Option {
return func(o *Options) {
o.Logger = val
}
+182
View File
@@ -0,0 +1,182 @@
package frontend
import (
"encoding/json"
"fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog"
"io"
"log"
"net/http"
"regexp"
"strings"
"github.com/glauth/glauth/v2/pkg/assets"
)
// RunAPI provides a basic REST API
func RunAPI(opts ...Option) {
options := newOptions(opts...)
log := options.Logger
cfg := options.Config
register(http.DefaultServeMux, log, cfg.TLS, cfg.Listen)
if cfg.TLS {
log.Info().Str("address", cfg.Listen).Msg("Starting HTTPS server")
err := http.ListenAndServeTLS(cfg.Listen, cfg.Cert, cfg.Key, nil)
if err != nil {
log.Error().Err(err).Msg("error starting HTTPS server")
return
}
} else {
log.Info().Str("address", cfg.Listen).Msg("Starting HTTP server")
err := http.ListenAndServe(cfg.Listen, nil)
if err != nil {
log.Error().Err(err).Msg("error starting HTTP server")
return
}
}
}
func register(mux *http.ServeMux, log zerolog.Logger, tls bool, listen string) {
fs := http.FileServer(http.FS(assets.Content))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Info().Str("path", r.URL.Path).Msg("Web")
if r.URL.Path != "/" {
log.Info().Msg("Web 404")
http.NotFound(w, r)
return
}
fs.ServeHTTP(w, r)
})
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
var url string
if tls {
url = fmt.Sprintf("https://%s/debug/vars", listen)
} else {
url = fmt.Sprintf("http://%s/debug/vars", listen)
}
c := &collector{
url: url,
names: map[string]string{
"proxy": "glauth_proxy",
"proxy_frontend": "glauth_proxy_frontend",
"proxy_backend": "glauth_proxy_backend",
},
helps: map[string]string{
"proxy": "General Metrics",
"proxy_frontend": "Frontend Metrics",
"proxy_backend": "Backend Metrics",
},
labelName: map[string]string{
"proxy": "metric",
"proxy_frontend": "metric",
"proxy_backend": "metric",
},
}
prometheus.MustRegister(c)
mux.Handle("/metrics", promhttp.Handler())
}
// The code below liberally borrows from https://github.com/albertito/prometheus-expvar-exporter
// and is adapted (just a little) for our purpose.
type collector struct {
url string
names map[string]string
helps map[string]string
labelName map[string]string
}
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
return
}
func (c *collector) Collect(ch chan<- prometheus.Metric) {
resp, err := http.Get(c.url)
if err != nil {
log.Printf("Error scraping %q: %v", c.url, err)
return
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("Error reading body of %q: %v", c.url, err)
return
}
// Replace "\xNN" with "?" because the default parser doesn't handle them
// well.
re := regexp.MustCompile(`\\x..`)
body = re.ReplaceAllFunc(body, func(s []byte) []byte {
return []byte("?")
})
var vs map[string]interface{}
err = json.Unmarshal(body, &vs)
if err != nil {
log.Printf("Error unmarshalling json from %q: %v", c.url, err)
return
}
for k, v := range vs {
name := strings.ReplaceAll(k, "/", "_")
if n, ok := c.names[k]; ok {
name = n
}
help := fmt.Sprintf("expvar %q", k)
if h, ok := c.helps[k]; ok {
help = h
}
lnames := []string{}
if ln, ok := c.labelName[k]; ok {
lnames = append(lnames, ln)
}
desc := prometheus.NewDesc(name, help, lnames, nil)
switch v := v.(type) {
case float64:
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue, v)
case bool:
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue,
valToFloat(v))
case map[string]interface{}:
// We only support explicitly written label names.
if len(lnames) != 1 {
continue
}
for lk, lv := range v {
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue,
valToFloat(lv), lk)
}
case string:
// Not supported by Prometheus.
continue
case []interface{}:
// Not supported by Prometheus.
continue
default:
continue
}
}
}
func valToFloat(v interface{}) float64 {
switch v := v.(type) {
case float64:
return v
case bool:
if v {
return 1.0
}
return 0.0
case string:
return 0.0
}
log.Printf("unexpected value type: %#v", v)
return 0.0
}
+125
View File
@@ -0,0 +1,125 @@
package frontend
import (
"bytes"
"crypto/sha256"
"github.com/rs/zerolog"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/glauth/glauth/v2/pkg/assets"
)
// TestAPI is a small test to make sure that behavior didn't change for the
// worse when switching over to the go1.16+ "embed" package from go-bindata.
func TestAPI(t *testing.T) {
tt := []struct {
Name string
Path string
Check func(testing.TB, *http.Response)
}{
{
Name: "Root",
Path: "/",
Check: func(t testing.TB, res *http.Response) {
if res.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v", res.Status)
return
}
h := sha256.New()
if _, err := io.Copy(h, res.Body); err != nil {
t.Error(err)
}
if got, want := h.Sum(nil), mkDigest(t, "index.html"); !bytes.Equal(got, want) {
t.Errorf("bad digest: got: %x, want %x", got, want)
}
},
},
{
Name: "NotFound",
Path: "/glauth.js",
Check: func(t testing.TB, res *http.Response) {
if res.StatusCode == http.StatusNotFound {
return
}
t.Errorf("unexpected response: %v", res.Status)
},
},
{
Name: "Content-Type",
Path: "/assets/glauth.js",
Check: func(t testing.TB, res *http.Response) {
if res.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v", res.Status)
}
if got, want := res.Header.Get("Content-Type"), `application/javascript`; got != want {
t.Errorf("bad content-type: got: %q, want %q", got, want)
}
h := sha256.New()
if _, err := io.Copy(h, res.Body); err != nil {
t.Error(err)
}
if got, want := h.Sum(nil), mkDigest(t, "glauth.js"); !bytes.Equal(got, want) {
t.Errorf("bad digest: got: %x, want %x", got, want)
}
},
},
{
Name: "Content-Type",
Path: "/assets/glauth.css",
Check: func(t testing.TB, res *http.Response) {
if res.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v", res.Status)
}
// This isn't exactly the same, it adds a charset argument.
if got, want := res.Header.Get("Content-Type"), `text/css; charset=utf-8`; got != want {
t.Errorf("bad content-type: got: %q, want %q", got, want)
}
h := sha256.New()
if _, err := io.Copy(h, res.Body); err != nil {
t.Error(err)
}
if got, want := h.Sum(nil), mkDigest(t, "glauth.css"); !bytes.Equal(got, want) {
t.Errorf("bad digest: got: %x, want %x", got, want)
}
},
},
}
mux := http.NewServeMux()
log := zerolog.New(os.Stdout).Level(zerolog.InfoLevel)
register(mux, log, true, "0.0.0.0:5555")
srv := httptest.NewServer(mux)
defer srv.Close()
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, srv.URL+tc.Path, nil)
if err != nil {
t.Fatal(err)
}
res, err := srv.Client().Do(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
tc.Check(t, res)
})
}
}
func mkDigest(t testing.TB, path string) []byte {
f, err := assets.Content.Open(path)
if err != nil {
t.Error(err)
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
t.Error(err)
}
return h.Sum(nil)
}
@@ -2,21 +2,21 @@ package handler
import (
"fmt"
"github.com/rs/zerolog"
"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/glauth/glauth/v2/pkg/config"
"github.com/glauth/glauth/v2/pkg/stats"
"github.com/nmcclain/ldap"
)
type configHandler struct {
backend config.Backend
log logr.Logger
log *zerolog.Logger
cfg *config.Config
yubikeyAuth *yubigo.YubiAuth
ldohelper LDAPOpsHelper
@@ -41,7 +41,7 @@ func NewConfigHandler(opts ...Option) Handler {
func (h configHandler) GetBackend() config.Backend {
return h.backend
}
func (h configHandler) GetLog() logr.Logger {
func (h configHandler) GetLog() *zerolog.Logger {
return h.log
}
func (h configHandler) GetCfg() *config.Config {
@@ -111,12 +111,12 @@ func (h configHandler) FindGroup(groupName string) (f bool, g config.Group, err
return found, group, nil
}
func (h configHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error) {
func (h configHandler) FindPosixAccounts(hierarchy string) (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: h.backend.NameFormat, Values: []string{u.Name}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "uid", Values: []string{u.Name}})
if len(u.GivenName) > 0 {
@@ -171,26 +171,57 @@ func (h configHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error)
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)
if len(u.CustomAttrs) > 0 {
for key, attr := range u.CustomAttrs {
switch typedattr := attr.(type) {
case []interface{}:
var values []string
for _, v := range typedattr {
switch typedvalue := v.(type) {
case string:
values = append(values, MaybeDecode(typedvalue))
default:
values = append(values, MaybeDecode(fmt.Sprintf("%v", typedvalue)))
}
}
attrs = append(attrs, &ldap.EntryAttribute{Name: key, Values: values})
default:
h.log.Warn().Str("key", key).Interface("value", attr).Msg("Unable to map custom attribute")
}
}
}
var dn string
if hierarchy == "" {
dn = fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
} else {
dn = fmt.Sprintf("%s=%s,%s=%s,%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), hierarchy, h.backend.BaseDN)
}
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
return entries, nil
}
func (h configHandler) FindPosixGroups() (entrylist []*ldap.Entry, err error) {
func (h configHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entry, err error) {
asGroupOfUniqueNames := hierarchy == "ou=groups"
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: h.backend.GroupFormat, 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)
attrs = append(attrs, &ldap.EntryAttribute{Name: "uniqueMember", Values: h.getGroupMemberDNs(g.GIDNumber)})
if asGroupOfUniqueNames {
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"groupOfUniqueNames", "top"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: h.getGroupMemberIDs(g.GIDNumber)})
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixGroup", "top"}})
}
dn := fmt.Sprintf("%s=%s,%s,%s", h.backend.GroupFormat, g.Name, hierarchy, h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
@@ -203,16 +234,22 @@ func (h configHandler) Close(boundDn string, conn net.Conn) error {
return nil
}
func (h configHandler) getGroupMembers(gid int) []string {
func (h configHandler) getGroupMemberDNs(gid int) []string {
var insertOuUsers string
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
insertOuUsers = ""
} else {
insertOuUsers = ",ou=users"
}
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)
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, 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)
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
members[dn] = true
}
}
@@ -223,7 +260,7 @@ func (h configHandler) getGroupMembers(gid int) []string {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid != gid {
includegroupmembers := h.getGroupMembers(includegroupid)
includegroupmembers := h.getGroupMemberDNs(includegroupid)
for _, includegroupmember := range includegroupmembers {
members[includegroupmember] = true
@@ -261,7 +298,7 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid {
h.log.V(2).Info("Ignoring myself as included group", "groupid", includegroupid)
h.log.Warn().Int("groupid", includegroupid).Msg("Ignoring myself as included group")
} else {
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
@@ -289,7 +326,7 @@ func (h configHandler) getGroupDNs(gids []int) []string {
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)
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, g.Name, h.backend.BaseDN)
groups[dn] = true
}
@@ -1,7 +1,7 @@
package handler
import (
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/v2/pkg/config"
"github.com/nmcclain/ldap"
)
+18
View File
@@ -0,0 +1,18 @@
package handler
import (
"encoding/base64"
"strings"
)
func MaybeDecode(value string) string {
if strings.HasPrefix(value, "base64: ") {
udec, err := base64.StdEncoding.DecodeString(value[8:])
if err != nil {
value = "* invalid value *"
} else {
value = string(udec)
}
}
return value
}
+30 -35
View File
@@ -5,6 +5,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/rs/zerolog"
"net"
"net/url"
"os"
@@ -14,9 +15,8 @@ import (
"sync"
"time"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/glauth/glauth/v2/pkg/config"
"github.com/glauth/glauth/v2/pkg/stats"
"github.com/nmcclain/ldap"
"github.com/pquerna/otp/totp"
)
@@ -28,7 +28,7 @@ type ldapHandler struct {
backend config.Backend
handlers HandlerWrapper
doPing chan bool
log logr.Logger
log *zerolog.Logger
lock *sync.Mutex // for sessions and servers
sessions map[string]ldapSession
servers []ldapBackend
@@ -76,7 +76,7 @@ func NewLdapHandler(opts ...Option) Handler {
for _, ldapurl := range handler.backend.Servers {
l, err := parseURL(ldapurl)
if err != nil {
handler.log.Error(err, "could not parse url")
handler.log.Error().Err(err).Msg("could not parse url")
os.Exit(1)
}
handler.servers = append(handler.servers, l)
@@ -88,9 +88,8 @@ func NewLdapHandler(opts ...Option) Handler {
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())
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind request")
// if h.helper != nil {
if true {
@@ -132,7 +131,7 @@ func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCod
}
if !validotp {
h.log.V(6).Info(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
h.log.Debug().Msg(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultInvalidCredentials, nil
}
}
@@ -141,25 +140,24 @@ func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCod
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)
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Err(err).Msg("could not get session")
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())
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid creds")
return ldap.LDAPResultInvalidCredentials, nil
}
stats.Frontend.Add("bind_successes", 1)
h.log.V(6).Info("bind success", "binddn", bindDN, "src", conn.RemoteAddr())
h.log.Debug().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("bind success")
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)
h.log.Debug().Str("binddn", boundDN).Str("src", conn.RemoteAddr().String()).Str("filter", searchReq.Filter).Msg("Search request")
// "1.1" has special meaning: it does what an empty attribute list would do
// if it didn't already mean "return all attributes"
@@ -193,19 +191,19 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
searchReq.Controls,
)
h.log.V(6).Info("Search request to backend", "request", search)
h.log.Debug().Interface("request", search).Msg("Search request to backend")
sr, err := s.ldap.Search(search)
h.log.V(6).Info("Backend Search result", "result", sr)
h.log.Debug().Interface("result", sr).Msg("Backend Search result")
if !wantAttributes {
h.log.V(6).Info("AP: Search Info", "type", "No attributes")
h.log.Debug().Str("type", "No attributes").Msg("AP: Search Info")
for _, entry := range sr.Entries {
entry.Attributes = entry.Attributes[:0]
}
}
if wantTypesOnly {
h.log.V(6).Info("AP: Search Info", "type", "Types only")
h.log.Debug().Str("type", "Types only").Msg("AP: Search Info")
for _, entry := range sr.Entries {
for _, attribute := range entry.Attributes {
attribute.Values = attribute.Values[:0]
@@ -232,7 +230,7 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
// 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")
h.log.Debug().Str("type", "Root search detected").Msg("AP: Search Info")
}
filters := h.buildReqAttributesList(searchReq.Filter, []string{})
@@ -261,16 +259,16 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
Referrals: sr.Referrals,
Controls: sr.Controls,
}
h.log.V(6).Info("Frontend Search result", "result", ssr)
h.log.Debug().Interface("result", ssr).Msg("Frontend Search result")
if err != nil {
e := err.(*ldap.Error)
h.log.V(6).Info("Search Err", "error", err)
h.log.Debug().Err(err).Msg("search 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))
h.log.Debug().Str("filter", search.Filter).Int("numentries", len(ssr.Entries)).Msg("AP: Search OK")
return ssr, nil
}
@@ -334,7 +332,7 @@ func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
func (h *ldapHandler) monitorServers() {
err := h.ping()
if err != nil {
h.log.Error(err, "could not ping server")
h.log.Error().Err(err).Msg("could not ping server")
os.Exit(1)
// TODO return error
}
@@ -342,18 +340,18 @@ func (h *ldapHandler) monitorServers() {
for {
select {
case <-h.doPing:
h.log.V(3).Info("doPing requested due to server failure")
h.log.Warn().Msg("doPing requested due to server failure")
err = h.ping()
if err != nil {
h.log.Error(err, "could not ping server")
h.log.Error().Err(err).Msg("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")
h.log.Warn().Msg("doPing after timeout")
err = h.ping()
if err != nil {
h.log.Error(err, "could not ping server")
h.log.Error().Err(err).Msg("could not ping server")
os.Exit(1)
// TODO return error
}
@@ -362,7 +360,6 @@ func (h *ldapHandler) monitorServers() {
}()
}
//
func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
id := connID(conn)
h.lock.Lock()
@@ -399,7 +396,6 @@ func (h ldapHandler) getSession(conn net.Conn) (ldapSession, error) {
return s, nil
}
//
func (h ldapHandler) ping() error {
healthy := false
for k, s := range h.servers {
@@ -419,7 +415,7 @@ func (h ldapHandler) ping() error {
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.log.Fatal().Str("hostname", s.Hostname).Int("port", s.Port).Err(err).Msg("server ping failed")
h.servers[k].Ping = 0
h.servers[k].Status = Down
} else {
@@ -430,19 +426,18 @@ func (h ldapHandler) ping() error {
}
h.lock.Unlock()
}
h.log.V(6).Info("Server health", "servers", h.servers)
h.log.Debug().Interface("servers", h.servers).Msg("Server health")
b, err := json.Marshal(h.servers)
if err != nil {
h.log.V(1).Info("Error encoding tail data", "error", err)
h.log.Fatal().Err(err).Msg("error encoding tail data")
}
stats.Backend.Set("servers", stats.Stringer(string(b)))
if healthy == false {
return fmt.Errorf("No healthy servers")
return fmt.Errorf("no healthy servers")
}
return nil
}
//
func (h ldapHandler) getBestServer() (ldapBackend, error) {
favorite := ldapBackend{}
forever, err := time.ParseDuration("30m")
@@ -459,7 +454,7 @@ func (h ldapHandler) getBestServer() (ldapBackend, error) {
if bestping == forever {
return ldapBackend{}, fmt.Errorf("No healthy servers found")
}
h.log.V(6).Info("Best server", "favorite", favorite)
h.log.Debug().Interface("favorite", favorite).Msg("Best server")
return favorite, nil
}
@@ -481,7 +476,7 @@ func parseURL(ldapurl string) (ldapBackend, error) {
} else if u.Scheme == "ldap" {
port = 389
} else {
return ldapBackend{}, fmt.Errorf("Unknown LDAP scheme: %s", u.Scheme)
return ldapBackend{}, fmt.Errorf("unknown LDAP scheme: %s", u.Scheme)
}
parts := strings.Split(u.Host, ":")
hostname := parts[0]
+774
View File
@@ -0,0 +1,774 @@
package handler
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/rs/zerolog"
"net"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/v2/pkg/config"
"github.com/glauth/glauth/v2/pkg/stats"
"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() *zerolog.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(hierarchy string) (entrylist []*ldap.Entry, err error)
FindPosixGroups(hierarchy string) (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)
h.GetLog().Info().Str("binddn", bindDN).Str("basedn", h.GetBackend().BaseDN).Str("src", conn.RemoteAddr().String()).Msg("Bind request")
stats.Frontend.Add("bind_reqs", 1)
// Special Case: bind as anonymous
if bindDN == "" && bindSimplePw == "" {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().Info().Str("src", conn.RemoteAddr().String()).Msg("Anonymous Bind success")
return ldap.LDAPResultSuccess, nil
}
user, ldapcode := l.findUser(h, bindDN, true /* checkGroup */)
if ldapcode != ldap.LDAPResultSuccess {
return ldapcode, 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 is 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().Info().Str("incorrect stored hash", "(omitted)").Msg("invalid app credentials")
} else {
if bcrypt.CompareHashAndPassword(decoded, []byte(untouchedBindSimplePw)) == nil {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().Info().Int("index", index).Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success using app pw")
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().Info().Int("index", index).Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Attempt to bind app pw failed")
} else {
stats.Frontend.Add("bind_successes", 1)
h.GetLog().Info().Int("index", index).Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success using app pw")
return ldap.LDAPResultSuccess, nil
}
}
}
if user.PassAppCustom != nil {
err := user.PassAppCustom(user, untouchedBindSimplePw)
if err != nil {
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Str("error", err.Error()).Msg("Attempt to bind app custom auth failed")
return ldap.LDAPResultInvalidCredentials, nil
}
stats.Frontend.Add("bind_successes", 1)
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success using app custom auth")
return ldap.LDAPResultSuccess, nil
}
// Then ensure the OTP is valid before checking
if !validotp {
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid OTP token")
return ldap.LDAPResultInvalidCredentials, nil
}
// Now, check the pasword hash
if user.PassBcrypt != "" {
decoded, err := hex.DecodeString(user.PassBcrypt)
if err != nil {
h.GetLog().Info().Str("incorrect stored hash", "(omitted)").Msg("invalid credentials")
return ldap.LDAPResultInvalidCredentials, nil
}
if bcrypt.CompareHashAndPassword(decoded, []byte(bindSimplePw)) != nil {
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid credentials")
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().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("invalid credentials")
l.maybePutInTimeout(h, conn, true)
return ldap.LDAPResultInvalidCredentials, nil
}
}
stats.Frontend.Add("bind_successes", 1)
h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success")
return ldap.LDAPResultSuccess, nil
}
/*
* TODO #1:
* Is it possible to map, on-the-fly, ou= -> cn= to maintain backware compatibility? Could be a switch...
* Or maybe sinmply configure in the .cfg file using the nameformat and groupformat settings?
* In 3.0 we could change default from cn to ou
* TODO #2: DONE
* Returns values when scope==base or scope==sub on a group entry
* TODO #3: DONE
* Make sure that when scope==sub, we do not always return, but augment results instead
* TODO #4: DONE
* Handle groups as two distinct objectclasses like OLDAP does
* Q: Does OLDAP return the groups twice when querying root+sub?
* TODO #5:
* Document roll out of schemas
*/
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)
searchBaseDN := strings.ToLower(searchReq.BaseDN)
anonymous := len(bindDN) < 1
var boundUser *config.User
var ldapcode ldap.LDAPResultCode
if !anonymous {
if bindDN, boundUser, ldapcode = l.searchCheckBindDN(h, baseDN, bindDN, anonymous); ldapcode != ldap.LDAPResultSuccess {
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: Potential bypass of BindDN %s", bindDN)
}
}
h.GetLog().Info().Str("binddn", bindDN).Str("basedn", baseDN).Str("searchbasedn", searchBaseDN).Str("src", conn.RemoteAddr().String()).Int("scope", searchReq.Scope).Str("filter", searchReq.Filter).Msg("Search request")
stats.Frontend.Add("search_reqs", 1)
switch entries, ldapcode := l.searchMaybeRootDSEQuery(h, baseDN, searchBaseDN, searchReq, anonymous); ldapcode {
case ldap.LDAPResultUnwillingToPerform:
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: No BaseDN provided")
case ldap.LDAPResultInsufficientAccessRights:
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Root DSE Search Error: Anonymous BindDN not allowed %s", bindDN)
case ldap.LDAPResultSuccess:
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
}
// Past this point, there is no reason to allow anonymous searches
if anonymous {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", bindDN)
}
switch entries, ldapcode, attributename := l.searchMaybeSchemaQuery(h, baseDN, searchBaseDN, searchReq, anonymous); ldapcode {
case ldap.LDAPResultOperationsError:
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Schema Error: attribute %s cannot be read", attributename)
case ldap.LDAPResultSuccess:
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
}
// Past this further point, we are looking at tree searches... not all standard searches yet, though
// But first, let's only allow legal searches
if !strings.HasSuffix(bindDN, fmt.Sprintf(",%s", baseDN)) {
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)
}
// Unless globally ignored, we will check that a user has capabilities allowing them to perform a search in the requested BaseDN
if !h.GetCfg().Behaviors.IgnoreCapabilities && !l.checkCapability(*boundUser, "search", []string{"*", searchBaseDN}) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: no capability allowing BindDN %s to perform search in %s", bindDN, searchBaseDN)
}
switch entries, ldapcode := l.searchMaybeTopLevelNodes(h, baseDN, searchBaseDN, searchReq); ldapcode {
case ldap.LDAPResultSuccess:
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
}
switch entries, ldapcode := l.searchMaybeTopLevelGroupsNode(h, baseDN, searchBaseDN, searchReq); ldapcode {
case ldap.LDAPResultSuccess:
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
}
switch entries, ldapcode := l.searchMaybeTopLevelUsersNode(h, baseDN, searchBaseDN, searchReq); ldapcode {
case ldap.LDAPResultSuccess:
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
}
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 entries, ldapcode := l.searchMaybePosixGroups(h, baseDN, searchBaseDN, searchReq, filterEntity); ldapcode {
case ldap.LDAPResultSuccess:
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
}
switch entries, ldapcode := l.searchMaybePosixAccounts(h, baseDN, searchBaseDN, searchReq, filterEntity); ldapcode {
case ldap.LDAPResultSuccess:
stats.Frontend.Add("search_successes", 1)
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Search OK")
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldapcode}, nil
}
// So, this should be an ERROR condition! Right..?
entries := []*ldap.Entry{}
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
// Returns: LDAPResultSuccess or any ldap code returned by findUser
func (l LDAPOpsHelper) searchCheckBindDN(h LDAPOpsHandler, baseDN string, bindDN string, anonymous bool) (newBindDN string, boundUser *config.User, ldapresultcode ldap.LDAPResultCode) {
boundUser, ldapcode := l.findUser(h, bindDN, false /* checkGroup */)
if ldapcode != ldap.LDAPResultSuccess {
return "", nil, ldapcode
}
// 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) {
// cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
bindDN = fmt.Sprintf("cn=%s,%s", boundUser.Name, baseDN)
}
return bindDN, boundUser, ldap.LDAPResultSuccess
}
// Search RootDSE and return information on the server
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultUnwillingToPerform, LDAPResultInsufficientAccessRights
func (l LDAPOpsHelper) searchMaybeRootDSEQuery(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, anonymous bool) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
if searchBaseDN != "" {
return nil, ldap.LDAPResultOther // OK
}
/// Only base scope searches allowed if no basedn is provided
if searchReq.Scope != ldap.ScopeBaseObject {
h.GetLog().Info().Interface("src", searchReq.Controls).Msg("Search Error: No BaseDN provided")
return nil, ldap.LDAPResultUnwillingToPerform // KO
}
if anonymous && !h.GetBackend().AnonymousDSE {
return nil, ldap.LDAPResultInsufficientAccessRights // KO
}
h.GetLog().Info().Str("special case", "root DSE").Msg("Search request")
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().Info().Str("filter", searchReq.Filter).Msg("AP: Root Search OK")
return entries, ldap.LDAPResultSuccess
}
// Search and return the information, after indirection from the RootDSE
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
func (l LDAPOpsHelper) searchMaybeSchemaQuery(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, anonymous bool) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode, attributename *string) {
if searchBaseDN != "cn=schema" {
return nil, ldap.LDAPResultOther, nil // OK
}
h.GetLog().Info().Str("special case", "schema discovery").Msg("Search request")
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, _ := os.ReadDir("schema")
for _, filename := range filenames {
attributename := new(string)
*attributename = filename.Name()
file, err := os.Open(filepath.Join("schema", *attributename))
if err != nil {
return nil, ldap.LDAPResultOperationsError, attributename
}
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().Info().Str("filter", searchReq.Filter).Msg("AP: Schema Discovery OK")
return entries, ldap.LDAPResultSuccess, nil
}
// Retrieve the top-levell nodes, i.e. the baseDN, groups, members...
// Returns: LDAPResultSuccess, LDAPResultOther
func (l LDAPOpsHelper) searchMaybeTopLevelNodes(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
if baseDN != searchBaseDN {
return nil, ldap.LDAPResultOther // OK
}
h.GetLog().Info().Str("special case", "top-level browse").Msg("Search request")
entries := []*ldap.Entry{}
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
entries = append(entries, l.topLevelRootNode(searchBaseDN))
}
entries = append(entries, l.topLevelGroupsNode(searchBaseDN, "groups"))
entries = append(entries, l.topLevelUsersNode(searchBaseDN))
if searchReq.Scope == ldap.ScopeWholeSubtree {
groupentries, err := h.FindPosixGroups("ou=users")
if err != nil {
return nil, ldap.LDAPResultOperationsError
}
entries = append(entries, groupentries...)
userentries, err := h.FindPosixAccounts("ou=users")
if err != nil {
return nil, ldap.LDAPResultOperationsError
}
entries = append(entries, userentries...)
}
stats.Frontend.Add("search_successes", 1)
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Top-Level Browse OK")
return entries, ldap.LDAPResultSuccess
}
// Search starting from and including the ou=groups node
// Returns: LDAPResultSuccess, LDAPResultOther
func (l LDAPOpsHelper) searchMaybeTopLevelGroupsNode(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
if searchBaseDN != fmt.Sprintf("ou=groups,%s", baseDN) {
return nil, ldap.LDAPResultOther // OK
}
h.GetLog().Info().Str("special case", "top-level groups node").Msg("Search request")
entries := []*ldap.Entry{}
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
entries = append(entries, l.topLevelGroupsNode(searchBaseDN, "groups"))
}
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
groupentries, err := h.FindPosixGroups("ou=groups")
if err != nil {
return nil, ldap.LDAPResultOperationsError
}
entries = append(entries, groupentries...)
}
stats.Frontend.Add("search_successes", 1)
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Top-Level Groups Browse OK")
return entries, ldap.LDAPResultSuccess
}
// Search starting from and including the ou=users node
// Returns: LDAPResultSuccess, LDAPResultOther
func (l LDAPOpsHelper) searchMaybeTopLevelUsersNode(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
if searchBaseDN != fmt.Sprintf("ou=users,%s", baseDN) {
return nil, ldap.LDAPResultOther // OK
}
h.GetLog().Info().Str("special case", "top-level users node").Msg("Search request")
entries := []*ldap.Entry{}
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
entries = append(entries, l.topLevelUsersNode(searchBaseDN))
}
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
groupentries, err := h.FindPosixGroups("ou=users")
if err != nil {
return nil, ldap.LDAPResultOperationsError
}
entries = append(entries, groupentries...)
}
if searchReq.Scope == ldap.ScopeWholeSubtree {
userentries, err := h.FindPosixAccounts("ou=users")
if err != nil {
return nil, ldap.LDAPResultOperationsError
}
entries = append(entries, userentries...)
}
stats.Frontend.Add("search_successes", 1)
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Top-Level Users Browse OK")
return entries, ldap.LDAPResultSuccess
}
// Look up posixgroup entries, either through objectlass or parent is ou=groups or ou=users
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
func (l LDAPOpsHelper) searchMaybePosixGroups(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, filterEntity string) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
hierarchy := "ou=groups"
if filterEntity != "posixgroup" {
bits := strings.Split(strings.Replace(searchBaseDN, baseDN, "", 1), ",")
if len(bits) != 3 || (bits[1] != "ou=groups" && bits[1] != "ou=users") {
return nil, ldap.LDAPResultOther // OK
}
hierarchy = bits[1]
}
h.GetLog().Info().Str("special case", "posix groups").Msg("Search request")
entries := []*ldap.Entry{}
if searchReq.Scope == ldap.ScopeBaseObject || searchReq.Scope == ldap.ScopeWholeSubtree {
groupentries, err := h.FindPosixGroups(hierarchy)
if err != nil {
return nil, ldap.LDAPResultOperationsError
}
entries = append(entries, l.preFilterEntries(searchBaseDN, groupentries)...)
}
if searchReq.Scope == ldap.ScopeSingleLevel || searchReq.Scope == ldap.ScopeWholeSubtree {
if hierarchy == "ou=users" {
userentries, err := h.FindPosixAccounts("ou=users")
if err != nil {
return nil, ldap.LDAPResultOperationsError
}
entries = append(entries, l.preFilterEntries(searchBaseDN, userentries)...)
}
}
stats.Frontend.Add("search_successes", 1)
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Posix Groups Search OK")
return entries, ldap.LDAPResultSuccess
}
// Lookup posixaccount entries
// Returns: LDAPResultSuccess, LDAPResultOther, LDAPResultOperationsError
// This function ignores scopes... for now
func (l LDAPOpsHelper) searchMaybePosixAccounts(h LDAPOpsHandler, baseDN string, searchBaseDN string, searchReq ldap.SearchRequest, filterEntity string) (resultentries []*ldap.Entry, ldapresultcode ldap.LDAPResultCode) {
switch filterEntity {
case "posixaccount", "shadowaccount", "":
h.GetLog().Info().Str("default case", filterEntity).Msg("Search request")
default:
return nil, ldap.LDAPResultOther // OK
}
// FixUp: we may be in the process of browsing users from a group ou
hierarchyString := ""
if strings.HasSuffix(searchBaseDN, fmt.Sprintf("ou=users,%s", baseDN)) {
hierarchyString = "ou=users"
}
unscopedEntries, err := h.FindPosixAccounts(hierarchyString)
if err != nil {
return nil, ldap.LDAPResultOperationsError
}
// Filter out entries, that are not in the search base dn
entries := []*ldap.Entry{}
for _, e := range unscopedEntries {
if strings.HasSuffix(e.DN, searchBaseDN) {
entries = append(entries, e)
}
}
stats.Frontend.Add("search_successes", 1)
h.GetLog().Info().Str("filter", searchReq.Filter).Msg("AP: Account Search OK")
return entries, ldap.LDAPResultSuccess
}
func (l LDAPOpsHelper) topLevelRootNode(searchBaseDN string) *ldap.Entry {
attrs := []*ldap.EntryAttribute{}
dnBits := strings.Split(searchBaseDN, ",")
for _, dnBit := range dnBits {
chunk := strings.Split(dnBit, "=")
attrs = append(attrs, &ldap.EntryAttribute{Name: chunk[0], Values: []string{chunk[1]}})
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "dcObject", "top"}})
return &ldap.Entry{DN: searchBaseDN, Attributes: attrs}
}
func (l LDAPOpsHelper) topLevelGroupsNode(searchBaseDN string, hierarchy string) *ldap.Entry {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{"groups"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "top"}})
hierarchyStringPrefix := fmt.Sprintf("ou=%s,", hierarchy)
dn := searchBaseDN
if !strings.HasPrefix(dn, hierarchyStringPrefix) {
dn = fmt.Sprintf("%s%s", hierarchyStringPrefix, dn)
}
return &ldap.Entry{DN: dn, Attributes: attrs}
}
func (l LDAPOpsHelper) topLevelUsersNode(searchBaseDN string) *ldap.Entry {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "ou", Values: []string{"users"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"organizationalUnit", "top"}})
dn := searchBaseDN
if !strings.HasPrefix(dn, "ou=users,") {
dn = fmt.Sprintf("ou=users,%s", dn)
}
return &ldap.Entry{DN: dn, Attributes: attrs}
}
// I am not quite sure why but I found out that, maybe due to my playing around with their DN,
// querying groups and users under a certain node (e.g. ou=users) with a scope of "sub"
// (and only in this scenario!) will defeat the LDAP library's filtering capabilities.
// Some day, hopefully, I'll fix this directly in the library.
func (l LDAPOpsHelper) preFilterEntries(searchBaseDN string, entries []*ldap.Entry) (resultentries []*ldap.Entry) {
filteredEntries := []*ldap.Entry{}
for _, entry := range entries {
if strings.HasSuffix(entry.DN, searchBaseDN) {
filteredEntries = append(filteredEntries, entry)
}
}
return filteredEntries
}
func (l LDAPOpsHelper) findUser(h LDAPOpsHandler, bindDN string, checkGroup bool) (userWhenFound *config.User, resultCode ldap.LDAPResultCode) {
var user config.User
baseDN := strings.ToLower("," + h.GetBackend().BaseDN)
// 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().Info().Str("userprincipalname", bindDN).Msg("User not found")
return nil, ldap.LDAPResultInvalidCredentials
}
} else {
// parse the bindDN - ensure that the bindDN ends with the BaseDN
if !strings.HasSuffix(bindDN, baseDN) {
h.GetLog().Info().Str("binddn", bindDN).Str("basedn", h.GetBackend().BaseDN).Msg("BindDN not part of our BaseDN")
// h.GetLog().Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, baseDN))
return nil, ldap.LDAPResultInvalidCredentials
}
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 || (len(parts) == 3 && parts[2] == "ou=users") {
userName = strings.TrimPrefix(parts[0], h.GetBackend().NameFormat+"=")
groupName = strings.TrimPrefix(parts[1], h.GetBackend().GroupFormat+"=")
} else {
h.GetLog().Info().Str("binddn", bindDN).Int("numparts", len(parts)).Msg("BindDN should have only one or two parts")
for _, part := range parts {
h.GetLog().Info().Str("part", part).Msg("Parts")
}
return nil, ldap.LDAPResultInvalidCredentials
}
// find the user
var foundUser bool // = false
foundUser, user, _ = h.FindUser(userName, false)
if !foundUser {
h.GetLog().Info().Str("username", userName).Msg("User not found")
return nil, ldap.LDAPResultInvalidCredentials
}
if checkGroup {
// find the group
var group config.Group // = nil
var foundGroup bool // = false
if groupName != "" {
foundGroup, group, _ = h.FindGroup(groupName)
if !foundGroup {
h.GetLog().Info().Str("groupname", groupName).Msg("Group not found")
return nil, ldap.LDAPResultInvalidCredentials
}
}
// validate group membership
if foundGroup {
if user.PrimaryGroup != group.GIDNumber {
h.GetLog().Info().Str("username", userName).Int("primarygroup", user.PrimaryGroup).Int("groupid", group.GIDNumber).Msg("primary group mismatch")
return nil, ldap.LDAPResultInvalidCredentials
}
}
}
}
return &user, ldap.LDAPResultSuccess
}
func (l LDAPOpsHelper) checkCapability(user config.User, action string, objects []string) bool {
for _, capability := range user.Capabilities {
if capability.Action == action {
for _, object := range objects {
if capability.Object == object {
return true
}
}
}
}
return false
}
// 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,
}
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) {
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()}
// if we now have 3 failed binds in a row
if len(info.failures) == cfg.Behaviors.NumberOfFailedBinds {
// 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) {
// 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]
}
@@ -2,10 +2,10 @@ package handler
import (
"context"
"github.com/rs/zerolog"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
"github.com/glauth/glauth/v2/pkg/config"
)
// Option defines a single option function.
@@ -15,7 +15,7 @@ type Option func(o *Options)
type Options struct {
Backend config.Backend
Handlers HandlerWrapper
Logger logr.Logger
Logger *zerolog.Logger
Config *config.Config
Context *context.Context
YubiAuth *yubigo.YubiAuth
@@ -34,7 +34,7 @@ func newOptions(opts ...Option) Options {
return opt
}
// newOptions initializes the available default options.
// NewOptions initializes the available default options.
func NewOptions(opts ...Option) Options {
opt := Options{}
@@ -52,7 +52,7 @@ func Backend(val config.Backend) Option {
}
}
// Our friendly handlers for all backends
// Handlers Our friendly handlers for all backends
func Handlers(val HandlerWrapper) Option {
return func(o *Options) {
o.Handlers = val
@@ -60,7 +60,7 @@ func Handlers(val HandlerWrapper) Option {
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
func Logger(val *zerolog.Logger) Option {
return func(o *Options) {
o.Logger = val
}
@@ -87,14 +87,14 @@ func YubiAuth(val *yubigo.YubiAuth) Option {
}
}
// If we specified a helper, for instance for OTP injection
// Helper 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
// LDAPHelper Global LDAP Handler
func LDAPHelper(val LDAPOpsHelper) Option {
return func(o *Options) {
o.LDAPHelper = val
@@ -6,21 +6,21 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"github.com/rs/zerolog"
"io"
"net"
"net/http"
"strings"
"sync"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/go-logr/logr"
"github.com/glauth/glauth/v2/pkg/config"
"github.com/glauth/glauth/v2/pkg/stats"
"github.com/nmcclain/ldap"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
type ownCloudSession struct {
log logr.Logger
log *zerolog.Logger
client *http.Client
user string
password string
@@ -29,7 +29,7 @@ type ownCloudSession struct {
}
type ownCloudHandler struct {
backend config.Backend
log logr.Logger
log *zerolog.Logger
client *http.Client
sessions map[string]ownCloudSession
lock *sync.Mutex
@@ -42,25 +42,25 @@ func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.
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())
h.log.Debug().Str("binddn", bindDN).Str("basedn", h.backend.BaseDN).Str("src", conn.RemoteAddr().String()).Msg("Bind request")
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)
h.log.Warn().Str("binddn", bindDN).Str("basedn", h.backend.BaseDN).Msg("BindDN not part of our 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))
h.log.Warn().Str("binddn", bindDN).Int("numparts", len(parts)).Msg("BindDN should have only one or two 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)
h.log.Warn().Str("username", userName).Str("basedn", h.backend.BaseDN).Msg("Login failed")
return ldap.LDAPResultInvalidCredentials, nil
}
@@ -79,7 +79,7 @@ func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.
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())
h.log.Debug().Str("binddn", bindDN).Str("basedn", h.backend.BaseDN).Str("src", conn.RemoteAddr().String()).Msg("Bind success")
return ldap.LDAPResultSuccess, nil
}
@@ -87,7 +87,7 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
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)
h.log.Debug().Str("binddn", bindDN).Str("basedn", baseDN).Str("src", conn.RemoteAddr().String()).Str("filter", searchReq.Filter).Msg("Search request")
stats.Frontend.Add("search_reqs", 1)
// validate the user is authenticated and has appropriate access
@@ -121,7 +121,7 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
}
for _, g := range groups {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{Name: "cn", Values: []string{*g.ID}})
attrs = append(attrs, &ldap.EntryAttribute{Name: h.backend.GroupFormat, 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"}})
@@ -133,7 +133,7 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: members})
}
dn := fmt.Sprintf("cn=%s,%s=groups,%s", *g.ID, h.backend.GroupFormat, h.backend.BaseDN)
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, *g.ID, h.backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
case "posixaccount", "":
@@ -146,12 +146,12 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
}
users, err := session.getUsers(userName)
if err != nil {
h.log.V(6).Info("Could not get user", "username", userName, "err", err)
h.log.Debug().Str("username", userName).Err(err).Msg("Could not get user")
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: h.backend.NameFormat, 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}})
@@ -168,7 +168,7 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
}
}
stats.Frontend.Add("search_successes", 1)
h.log.V(6).Info("AP: Search OK", "filter", searchReq.Filter)
h.log.Debug().Str("filter", searchReq.Filter).Msg("AP: Search OK")
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
@@ -219,7 +219,7 @@ func (h ownCloudHandler) login(name, pw string) bool {
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)
h.log.Error().Err(err).Int("status", resp.StatusCode).Msg("failed login")
return false
}
defer resp.Body.Close()
@@ -255,7 +255,7 @@ func (s ownCloudSession) getGroups() ([]msgraph.Group, error) {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@@ -302,21 +302,21 @@ func (s ownCloudSession) RoundTrip(req *http.Request) (*http.Response, error) {
func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
if s.useGraphAPI {
s.log.V(6).Info("using graph api")
s.log.Debug().Msg("using graph api")
ctx := context.Background()
req := s.NewClient().Users()
if len(userName) > 0 {
s.log.V(6).Info("fetching single user")
s.log.Debug().Msg("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")
s.log.Debug().Msg("fetching all users")
return req.Request().Get(ctx)
}
s.log.V(6).Info("using provisioning api")
s.log.Debug().Msg("using provisioning api")
usersUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/users?format=json", s.endpoint)
req, _ := http.NewRequest("GET", usersUrl, nil)
@@ -326,7 +326,7 @@ func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@@ -348,7 +348,7 @@ func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
}
func (s ownCloudSession) redirectPolicyFunc(req *http.Request, via []*http.Request) error {
s.log.V(6).Info("Setting user and password", "username", s.user)
s.log.Debug().Str("username", s.user).Msg("Setting user and password")
req.SetBasicAuth(s.user, s.password)
return nil
}
+84
View File
@@ -0,0 +1,84 @@
//go:build !windows
// +build !windows
package logging
import (
"fmt"
"github.com/rs/zerolog"
"io"
"log"
"log/syslog"
"os"
"regexp"
"strings"
"time"
)
// We will use this package to wrap log messages coming from libraries who have no interest
// in generating structured output.
var (
ldapliblogmatcher = regexp.MustCompile(`^\d{4}\/\d{1,2}\/\d{1,2} \d{1,2}\:\d{1,2}\:\d{1,2} `)
)
func InitLogging(reqdebug bool, reqsyslog bool, reqstructlog bool) zerolog.Logger {
var level zerolog.Level
if reqdebug {
level = zerolog.DebugLevel
} else {
level = zerolog.InfoLevel
}
var mainWriter io.Writer
if reqstructlog {
// Vroom vroom
mainWriter = os.Stderr
zerolog.TimeFieldFormat = time.RFC1123Z
} else {
// This is the inefficient writer
mainWriter = zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC1123Z}
}
var logr zerolog.Logger
if reqsyslog {
s, err := syslog.New(syslog.LOG_INFO, "glauth")
if err != nil {
fmt.Println("Unable to write to syslog: ignoring...")
reqsyslog = false
} else {
writers := zerolog.MultiLevelWriter(mainWriter, zerolog.SyslogLevelWriter(s))
logr = zerolog.New(writers).Level(level).With().Timestamp().Logger()
}
}
if !reqsyslog {
logr = zerolog.New(mainWriter).Level(level).With().Timestamp().Logger()
}
log.SetOutput(customWriter{logr: logr, structlog: reqstructlog})
return logr
}
type customWriter struct {
logr zerolog.Logger
structlog bool
}
func (e customWriter) Write(p []byte) (int, error) {
submatchall := ldapliblogmatcher.FindAllString(string(p), 1)
var msg string
for _, element := range submatchall {
msg = strings.TrimSpace(string(p[len(element):]))
}
if msg == "" {
msg = strings.TrimSpace(string(p))
}
if e.structlog {
fmt.Fprintf(os.Stderr, "{\"level\":\"info\",\"time\":\"%s\",\"message\":\"%s\"}\n", time.Now().Format(time.RFC1123Z), strings.Replace(strings.TrimSpace(msg), `"`, `\"`, -1))
} else {
e.logr.Info().Msg(msg)
}
return len(p), nil
}
+69
View File
@@ -0,0 +1,69 @@
//go:build windows
// +build windows
package logging
import (
"fmt"
"github.com/rs/zerolog"
"io"
"log"
"os"
"regexp"
"strings"
"time"
)
// We will use this package to wrap log messages coming from libraries who have no interest
// in generating structured output.
var (
ldapliblogmatcher = regexp.MustCompile(`^\d{4}\/\d{1,2}\/\d{1,2} \d{1,2}\:\d{1,2}\:\d{1,2} `)
)
func InitLogging(reqdebug bool, reqsyslog bool, reqstructlog bool) zerolog.Logger {
var level zerolog.Level
if reqdebug {
level = zerolog.DebugLevel
} else {
level = zerolog.InfoLevel
}
var mainWriter io.Writer
if reqstructlog {
// Vroom vroom
mainWriter = os.Stderr
zerolog.TimeFieldFormat = time.RFC1123Z
} else {
// This is the inefficient writer
mainWriter = zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC1123Z}
}
logr := zerolog.New(mainWriter).Level(level).With().Timestamp().Logger()
log.SetOutput(customWriter{logr: logr, structlog: reqstructlog})
return logr
}
type customWriter struct {
logr zerolog.Logger
structlog bool
}
func (e customWriter) Write(p []byte) (int, error) {
submatchall := ldapliblogmatcher.FindAllString(string(p), 1)
var msg string
for _, element := range submatchall {
msg = strings.TrimSpace(string(p[len(element):]))
}
if msg == "" {
msg = strings.TrimSpace(string(p))
}
if e.structlog {
fmt.Fprintf(os.Stderr, "{\"level\":\"info\",\"time\":\"%s\",\"message\":\"%s\"}\n", time.Now().Format(time.RFC1123Z), strings.Replace(strings.TrimSpace(msg), `"`, `\"`, -1))
} else {
e.logr.Info().Msg(msg)
}
return len(p), nil
}
@@ -1,9 +1,11 @@
package main
package plugins
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"github.com/rs/zerolog"
"net"
"os"
"regexp"
@@ -12,10 +14,9 @@ import (
"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/glauth/glauth/v2/pkg/config"
"github.com/glauth/glauth/v2/pkg/handler"
"github.com/glauth/glauth/v2/pkg/stats"
"github.com/nmcclain/ldap"
)
@@ -26,6 +27,8 @@ type SqlBackend interface {
GetDriverName() string
// Create db/schema if necessary
CreateSchema(db *sql.DB)
// Migrate schema if necessary
MigrateSchema(db *sql.DB, checker func(*sql.DB, string, string) bool)
//
GetPrepareSymbol() string
}
@@ -37,7 +40,7 @@ type database struct {
type databaseHandler struct {
backend config.Backend
log logr.Logger
log *zerolog.Logger
cfg *config.Config
yubikeyAuth *yubigo.YubiAuth
sqlBackend SqlBackend
@@ -47,19 +50,18 @@ type databaseHandler struct {
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)
options.Logger.Error().Err(err).Msg(fmt.Sprintf("unable to open SQL database named '%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)
options.Logger.Error().Err(err).Msg(fmt.Sprintf("unable to communicate with SQL database error: %s", options.Backend.Database))
os.Exit(1)
}
@@ -79,16 +81,24 @@ func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.H
attmatcher: configattributematcher}
sqlBackend.CreateSchema(db)
sqlBackend.MigrateSchema(db, ColumnExists)
options.Logger.V(3).Info("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
options.Logger.Info().Msg("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
return handler
}
func ColumnExists(db *sql.DB, tableName string, columnName string) bool {
var found string
err := db.QueryRow(fmt.Sprintf(`SELECT COUNT(%s) FROM %s`, columnName, tableName)).Scan(
&found)
return err == nil
}
func (h databaseHandler) GetBackend() config.Backend {
return h.backend
}
func (h databaseHandler) GetLog() logr.Logger {
func (h databaseHandler) GetLog() *zerolog.Logger {
return h.log
}
func (h databaseHandler) GetCfg() *config.Config {
@@ -132,14 +142,35 @@ func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u
user := config.User{}
found := false
var disabled int
err = h.database.cnx.QueryRow(fmt.Sprintf(`
SELECT u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey
SELECT u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.disabled
FROM users u WHERE %s=%s`,
criterion,
h.sqlBackend.GetPrepareSymbol()), userName).Scan(
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey, &disabled)
if err == nil {
found = true
user.Disabled = h.intToBool(disabled)
if !user.Disabled {
found = true
if !h.cfg.Behaviors.IgnoreCapabilities {
capability := config.Capability{}
rows, err := h.database.cnx.Query(fmt.Sprintf(`
SELECT c.action,c.object
FROM capabilities c WHERE userid=%s`,
h.sqlBackend.GetPrepareSymbol()), user.UIDNumber)
if err == nil {
for rows.Next() {
err := rows.Scan(&capability.Action, &capability.Object)
if err == nil {
user.Capabilities = append(user.Capabilities, capability)
}
}
}
defer rows.Close()
}
}
}
return found, user, err
@@ -150,7 +181,7 @@ func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, er
found := false
err = h.database.cnx.QueryRow(fmt.Sprintf(`
SELECT g.gidnumber FROM groups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
SELECT g.gidnumber FROM ldapgroups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
&group.GIDNumber)
if err == nil {
found = true
@@ -159,7 +190,7 @@ func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, er
return found, group, err
}
func (h databaseHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error) {
func (h databaseHandler) FindPosixAccounts(hierarchy string) (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
h.MemGroups, err = h.memoizeGroups()
@@ -168,7 +199,7 @@ func (h databaseHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error
}
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
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,u.sshkeys,u.custattr
FROM users u`)
if err != nil {
return entries, err
@@ -177,22 +208,52 @@ func (h databaseHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error
var otherGroups string
var disabled int
var sshKeys string
var custattrstr 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, &u.GivenName, &u.SN, &u.Mail, &u.LoginShell, &u.Homedir, &disabled)
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, &sshKeys, &custattrstr)
if err != nil {
return entries, err
}
u.OtherGroups = h.commaListToTable(otherGroups)
u.OtherGroups = h.commaListToIntTable(otherGroups)
u.Disabled = h.intToBool(disabled)
u.SSHKeys = h.commaListToStringTable(sshKeys)
entries = append(entries, h.getAccount(u))
entry := h.getAccount(hierarchy, u)
if custattrstr != "{}" {
var r map[string]interface{}
err := json.Unmarshal([]byte(custattrstr), &r)
if err != nil {
return entries, err
}
for key, attr := range r {
switch typedattr := attr.(type) {
case []interface{}:
var values []string
for _, v := range typedattr {
switch typedvalue := v.(type) {
case string:
values = append(values, handler.MaybeDecode(typedvalue))
default:
values = append(values, handler.MaybeDecode(fmt.Sprintf("%v", typedvalue)))
}
}
entry.Attributes = append(entry.Attributes, &ldap.EntryAttribute{Name: key, Values: values})
default:
h.log.Warn().Str("key", key).Interface("value", attr).Msg("Unable to map custom attribute")
}
}
}
entries = append(entries, entry)
}
return entries, nil
}
func (h databaseHandler) FindPosixGroups() (entrylist []*ldap.Entry, err error) {
func (h databaseHandler) FindPosixGroups(hierarchy string) (entrylist []*ldap.Entry, err error) {
entries := []*ldap.Entry{}
h.MemGroups, err = h.memoizeGroups()
@@ -201,7 +262,11 @@ func (h databaseHandler) FindPosixGroups() (entrylist []*ldap.Entry, err error)
}
for _, g := range h.MemGroups {
entries = append(entries, h.getGroup(g))
info := h.getGroup(hierarchy, g)
if hierarchy != "groups" {
info.DN = strings.Replace(info.DN, ",ou=groups,", fmt.Sprintf(",%s,", hierarchy), 1)
}
entries = append(entries, info)
}
return entries, nil
@@ -219,7 +284,7 @@ func (h databaseHandler) intToBool(value int) bool {
return true
}
func (h databaseHandler) commaListToTable(commaList string) []int {
func (h databaseHandler) commaListToIntTable(commaList string) []int {
if len(commaList) == 0 {
return make([]int, 0)
}
@@ -235,13 +300,20 @@ func (h databaseHandler) commaListToTable(commaList string) []int {
return rowsAsInts
}
func (h databaseHandler) commaListToStringTable(commaList string) []string {
if len(commaList) == 0 {
return make([]string, 0)
}
return strings.Split(commaList, ",")
}
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
SELECT g1.name,g1.gidnumber,ig.includegroupid
FROM ldapgroups g1
LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid
LEFT JOIN groups g2 ON ig.includegroupid=g2.gidnumber`)
LEFT JOIN ldapgroups g2 ON ig.includegroupid=g2.gidnumber`)
if err != nil {
return nil, errors.New("Unable to memoize groups list")
}
@@ -274,14 +346,18 @@ func (h databaseHandler) memoizeGroups() ([]config.Group, error) {
return memGroups, nil
}
// Used when looking up Posix Groups
func (h databaseHandler) getGroupMembers(gid int) []string {
func (h databaseHandler) getGroupMemberDNs(gid int) []string {
var insertOuUsers string
if h.cfg.Behaviors.LegacyVersion > 0 && h.cfg.Behaviors.LegacyVersion <= 20100 {
insertOuUsers = ""
} else {
insertOuUsers = ",ou=users"
}
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)=?`,
)
FROM users u`)
if err != nil {
// Silent fail... for now
return []string{}
@@ -296,13 +372,13 @@ func (h databaseHandler) getGroupMembers(gid int) []string {
return []string{}
}
if u.PrimaryGroup == gid {
dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
members[dn] = true
} else {
u.OtherGroups = h.commaListToTable(otherGroups)
u.OtherGroups = h.commaListToIntTable(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)
dn := fmt.Sprintf("%s=%s,%s=%s%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), insertOuUsers, h.backend.BaseDN)
members[dn] = true
}
}
@@ -313,7 +389,7 @@ func (h databaseHandler) getGroupMembers(gid int) []string {
if gid == g.GIDNumber {
for _, includegroupid := range g.IncludeGroups {
if includegroupid != gid {
includegroupmembers := h.getGroupMembers(includegroupid)
includegroupmembers := h.getGroupMemberDNs(includegroupid)
for _, includegroupmember := range includegroupmembers {
members[includegroupmember] = true
@@ -355,7 +431,7 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
if u.PrimaryGroup == gid {
members[u.Name] = true
} else {
u.OtherGroups = h.commaListToTable(otherGroups)
u.OtherGroups = h.commaListToIntTable(otherGroups)
for _, othergid := range u.OtherGroups {
if othergid == gid {
members[u.Name] = true
@@ -368,7 +444,7 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
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))
h.log.Warn().Msg(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
} else {
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
@@ -396,7 +472,7 @@ func (h databaseHandler) getGroupDNs(gids []int) []string {
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)
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, g.Name, h.backend.BaseDN)
groups[dn] = true
}
@@ -433,21 +509,27 @@ func (h databaseHandler) getGroupName(gid int) string {
}
// Toolbox
func (h databaseHandler) getGroup(g config.Group) *ldap.Entry {
func (h databaseHandler) getGroup(hierarchy string, g config.Group) *ldap.Entry {
asGroupOfUniqueNames := hierarchy == "ou=groups"
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{g.Name}})
attrs = append(attrs, &ldap.EntryAttribute{h.backend.GroupFormat, []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)
attrs = append(attrs, &ldap.EntryAttribute{"uniqueMember", h.getGroupMemberDNs(g.GIDNumber)})
if asGroupOfUniqueNames {
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"groupOfUniqueNames", "top"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{"memberUid", h.getGroupMemberIDs(g.GIDNumber)})
attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixGroup", "top"}})
}
dn := fmt.Sprintf("%s=%s,ou=groups,%s", h.backend.GroupFormat, g.Name, h.backend.BaseDN)
return &ldap.Entry{dn, attrs}
}
func (h databaseHandler) getAccount(u config.User) *ldap.Entry {
func (h databaseHandler) getAccount(hierarchy string, u config.User) *ldap.Entry {
attrs := []*ldap.EntryAttribute{}
attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{u.Name}})
attrs = append(attrs, &ldap.EntryAttribute{h.backend.NameFormat, []string{u.Name}})
attrs = append(attrs, &ldap.EntryAttribute{"uid", []string{u.Name}})
if len(u.GivenName) > 0 {
@@ -493,8 +575,11 @@ func (h databaseHandler) getAccount(u config.User) *ldap.Entry {
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)
var dn string
if hierarchy == "" {
dn = fmt.Sprintf("%s=%s,%s=%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
} else {
dn = fmt.Sprintf("%s=%s,%s=%s,%s,%s", h.backend.NameFormat, u.Name, h.backend.GroupFormat, h.getGroupName(u.PrimaryGroup), hierarchy, h.backend.BaseDN)
}
return &ldap.Entry{dn, attrs}
}
func main() {}
+3
View File
@@ -0,0 +1,3 @@
release-glauth-bogus:
@echo Ignoring bogus plugin
@@ -2,9 +2,9 @@ package server
import (
"context"
"github.com/rs/zerolog"
"github.com/glauth/glauth/pkg/config"
"github.com/go-logr/logr"
"github.com/glauth/glauth/v2/pkg/config"
)
// Option defines a single option function.
@@ -12,7 +12,7 @@ type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger logr.Logger
Logger zerolog.Logger
Config *config.Config
Context context.Context
}
@@ -29,7 +29,7 @@ func newOptions(opts ...Option) Options {
}
// Logger provides a function to set the logger option.
func Logger(val logr.Logger) Option {
func Logger(val zerolog.Logger) Option {
return func(o *Options) {
o.Logger = val
}
@@ -3,17 +3,17 @@ package server
import (
"errors"
"fmt"
"github.com/rs/zerolog"
"plugin"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/handler"
"github.com/go-logr/logr"
"github.com/glauth/glauth/v2/pkg/config"
"github.com/glauth/glauth/v2/pkg/handler"
"github.com/nmcclain/ldap"
)
type LdapSvc struct {
log logr.Logger
log zerolog.Logger
c *config.Config
yubiAuth *yubigo.YubiAuth
l *ldap.Server
@@ -46,7 +46,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
switch s.c.Helper.Datastore {
case "config":
helper = handler.NewConfigHandler(
handler.Logger(s.log),
handler.Logger(&s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
@@ -58,17 +58,17 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
}
nph, err := plug.Lookup(s.c.Helper.PluginHandler)
if err != nil {
return nil, errors.New("Unable to find 'NewPluginHandler' in loaded helper plugin")
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")
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.Logger(&s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
@@ -76,7 +76,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
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)
s.log.Info().Str("datastore", s.c.Helper.Datastore).Msg("Using helper")
}
backendCounter := -1
@@ -92,18 +92,18 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
h = handler.NewLdapHandler(
handler.Backend(backend),
handler.Handlers(allHandlers),
handler.Logger(s.log),
handler.Logger(&s.log),
handler.Helper(helper),
)
case "owncloud":
h = handler.NewOwnCloudHandler(
handler.Backend(backend),
handler.Logger(s.log),
handler.Logger(&s.log),
)
case "config":
h = handler.NewConfigHandler(
handler.Backend(backend),
handler.Logger(s.log),
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),
@@ -115,18 +115,18 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
}
nph, err := plug.Lookup(backend.PluginHandler)
if err != nil {
return nil, errors.New("Unable to find 'NewPluginHandler' in loaded backend plugin")
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")
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.Logger(&s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
handler.LDAPHelper(loh),
@@ -134,7 +134,7 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
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)
s.log.Info().Str("datastore", backend.Datastore).Int("position", i).Msg("Loading backend")
// Only our first backend will answer proper LDAP queries.
// Note that this could evolve towars something nicer where we would maintain
@@ -153,13 +153,13 @@ func NewServer(opts ...Option) (*LdapSvc, error) {
// 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)
s.log.Info().Str("address", s.c.LDAP.Listen).Msg("LDAP server listening")
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)
s.log.Info().Str("address", s.c.LDAPS.Listen).Msg("LDAPS server listening")
return s.l.ListenAndServeTLS(
s.c.LDAPS.Listen,
s.c.LDAPS.Cert,
+3 -1
View File
@@ -1,6 +1,8 @@
package stats
import "expvar"
import (
"expvar"
)
// exposed expvar variables
var (

Some files were not shown because too many files have changed in this diff Show More