mirror of
https://github.com/glauth/glauth.git
synced 2026-10-27 06:26:59 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28802ff87d | |||
| a02544ac8b | |||
| 3acd492e61 | |||
| bd9a5e47ee | |||
| 2b4aa3b78d | |||
| 45a2671f46 | |||
| 5e1ed70865 | |||
| d410b01955 |
@@ -0,0 +1,25 @@
|
||||
Thank you for making a pull request!
|
||||
|
||||
A few things to be aware of as you're working on your PR:
|
||||
|
||||
## WIP Tag
|
||||
Incomplete PRs are more than welcome - it can be useful to collaborate before
|
||||
implementation of an idea is complete. However, if your PR is not ready
|
||||
for merge, please add [WIP] to the end of the title (work-in-progress).
|
||||
|
||||
## Tests
|
||||
Before committing, you are encouraged to run the small but growing test
|
||||
suite. This is accomplished by `make test`. Additionally, if you are adding
|
||||
new functionality, consider adding tests covering your feature.
|
||||
|
||||
## CI
|
||||
Each push to a branch connected to a PR will be run through GLAuth's
|
||||
CI system. Please use these to your advantage. In particular, the Travis
|
||||
integration tests rely on the LDAP queries returning with a set result,
|
||||
so if your changes will change the output, Travis will likely fail.
|
||||
|
||||
To update, run `make fast && make updatetest && make test`. This will
|
||||
delete the output snapshots provided and make new ones. You can then
|
||||
inspect the changes and commit them.
|
||||
|
||||
Similarly, check codeclimate and try to fix what you find there if it fails.
|
||||
@@ -4,6 +4,9 @@ glauth.cfg
|
||||
# Ignore the built binary during testing
|
||||
glauth
|
||||
|
||||
# Ignore generated certs in this folder
|
||||
certs/
|
||||
|
||||
# Built binaries
|
||||
bin
|
||||
|
||||
|
||||
+10
-8
@@ -3,8 +3,9 @@ language: go
|
||||
# USE THIS alongside makefile tag detection: $TRAVIS_TAG
|
||||
|
||||
go:
|
||||
- 1.9.x # last version
|
||||
- 1.10.x # current version
|
||||
- 1.9.x # older version
|
||||
- 1.10.x # last version
|
||||
- 1.11.x # current version
|
||||
|
||||
# Speed up git clones
|
||||
git:
|
||||
@@ -15,18 +16,19 @@ git:
|
||||
# flunk the build and immediately stop. It's sorta like having
|
||||
# set -e enabled in bash.
|
||||
before_script:
|
||||
- sudo apt-get -qq update && sudo apt-get install -y ldap-utils oathtool # need this for integration tests
|
||||
- sudo apt-get -qq update && sudo apt-get install -y go-bindata # need this for makefile
|
||||
- travis_retry sudo apt-get -qq update && sudo apt-get install -y ldap-utils oathtool # need this for integration tests
|
||||
- travis_retry sudo apt-get -qq update && sudo apt-get install -y go-bindata # need this for makefile
|
||||
- GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) # All the .go files, excluding vendor/
|
||||
#- go get github.com/golang/lint/golint # Linter
|
||||
#- go get honnef.co/go/tools/cmd/megacheck # Badass static analyzer/linter
|
||||
# - go get github.com/fzipp/gocyclo # Not sure what this is
|
||||
#- travis_retry go get github.com/golang/lint/golint # Linter
|
||||
#- travis_retry go get honnef.co/go/tools/cmd/megacheck # Badass static analyzer/linter
|
||||
# - travis_retry go get github.com/fzipp/gocyclo # Not sure what this is
|
||||
|
||||
|
||||
# Run bindata for assets
|
||||
- make bindata
|
||||
|
||||
# Fetch dependencies
|
||||
- go get -t -v ./...
|
||||
- travis_retry go get -t -v ./...
|
||||
|
||||
|
||||
script:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
|
||||
|
||||
For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage.
|
||||
+5
-1
@@ -45,11 +45,15 @@ COPY --from=build /app/glauth /app/glauth
|
||||
COPY --from=build /app/scripts/docker/start.sh /app/docker/
|
||||
COPY --from=build /app/scripts/docker/default-config.cfg /app/docker/
|
||||
|
||||
# Install ldapsearch for container health checks, then ensure ldapsearch is installed
|
||||
RUN apk update && apk add --no-cache openldap-clients && which ldapsearch && rm -rf /var/cache/apk/*
|
||||
|
||||
# Install init
|
||||
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64 && chmod +x /usr/local/bin/dumb-init
|
||||
|
||||
# Expose web and LDAP ports
|
||||
EXPOSE 389 5555
|
||||
EXPOSE 389 636 5555
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||
CMD ["/bin/sh", "/app/docker/start.sh"]
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ fast: setup linux64 verify cleanup
|
||||
binaries: linux32 linux64 linuxarm32 linuxarm64 darwin64 win32 win64
|
||||
|
||||
# Setup commands to always run
|
||||
setup: bindata format
|
||||
setup: getdeps bindata format
|
||||
|
||||
#####################
|
||||
# Subcommands
|
||||
@@ -47,7 +47,12 @@ setup: bindata format
|
||||
runtest:
|
||||
./scripts/travis/integration-test.sh cleanup
|
||||
|
||||
# Get all dependencies
|
||||
getdeps:
|
||||
go get -d ./...
|
||||
|
||||
updatetest:
|
||||
./scripts/travis/integration-test.sh
|
||||
|
||||
bindata:
|
||||
go get -u github.com/jteeuwen/go-bindata/... && ${GOPATH}/bin/go-bindata -pkg=main assets && gofmt -w bindata.go
|
||||
@@ -70,7 +75,7 @@ linux64:
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "${BUILD_VARS}" -o bin/glauth64 ${BUILD_FILES} && cd bin && sha256sum glauth64 > glauth64.sha256
|
||||
|
||||
linuxarm32:
|
||||
GOOS=linux GOARCH=386 go build -ldflags "${BUILD_VARS}" -o bin/glauth-arm32 ${BUILD_FILES} && cd bin && sha256sum glauth-arm32 > glauth-arm32.sha256
|
||||
GOOS=linux GOARCH=arm go build -ldflags "${BUILD_VARS}" -o bin/glauth-arm32 ${BUILD_FILES} && cd bin && sha256sum glauth-arm32 > glauth-arm32.sha256
|
||||
|
||||
linuxarm64:
|
||||
GOOS=linux GOARCH=arm64 go build -ldflags "${BUILD_VARS}" -o bin/glauth-arm64 ${BUILD_FILES} && cd bin && sha256sum glauth-arm64 > glauth-arm64.sha256
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# GLAuth: LDAP authentication server for developers
|
||||
Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ configurable backends.
|
||||
|
||||
@@ -6,7 +5,7 @@ Go-lang LDAP Authentication (GLAuth) is a secure, easy-to-use, LDAP server w/ co
|
||||
[](https://github.com/glauth/glauth/graphs/commit-activity)
|
||||
|
||||
[](https://hub.docker.com/r/glauth/glauth/)
|
||||
[](https://hub.docker.com/r/glauth/glauth/)
|
||||
[](https://hub.docker.com/r/glauth/glauth/)
|
||||
|
||||
[](https://codeclimate.com/github/glauth/glauth/maintainability)
|
||||
[](https://codeclimate.com/github/glauth/glauth/test_coverage)
|
||||
@@ -40,8 +39,11 @@ 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
|
||||
|
||||
*make fast* - run build for only linux 64 bit
|
||||
|
||||
*make run* - wrapper for the 'go run' command, setting up the needed tooling
|
||||
|
||||
*make test* - run the integration test on linux64 binary
|
||||
|
||||
### Usage:
|
||||
@@ -92,14 +94,14 @@ In order to use S3, you must set your AWS credentials. Either:
|
||||
1. set the -K and -S command-line flags **OR**
|
||||
2. set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.
|
||||
|
||||
More configuration options are documented here: https://github.com/nmcclain/glauth/blob/master/sample-simple.cfg
|
||||
More configuration options are documented here: https://github.com/glauth/glauth/blob/master/sample-simple.cfg
|
||||
|
||||
### Required Fields
|
||||
* Name
|
||||
* The user's username
|
||||
* ou
|
||||
* ID of the user's primary group
|
||||
* uidbumber
|
||||
* uidnumber
|
||||
* The user's unix user id
|
||||
* sshPublicKey
|
||||
* Specify an array of public keys
|
||||
@@ -137,6 +139,10 @@ More configuration options are documented here: https://github.com/nmcclain/glau
|
||||
* Specify OTP secret used to validate OTP passcode
|
||||
* Example: 3hnvnk4ycv44glzigd6s25j4dougs3rk
|
||||
* default = blank
|
||||
* passappsha256
|
||||
* Specify an array of app passwords which can also succesfully bind - these bypass the OTP check. Hash the same way as password.
|
||||
* Example: ["c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa","4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88"]
|
||||
* default = blank
|
||||
* yubikey
|
||||
* Specify Yubikey ID for maching Yubikey OTP against the user
|
||||
* Example: cccjgjgkhcbb
|
||||
@@ -155,6 +161,11 @@ When using 2FA, append the 2FA code to the end of the password when authenticati
|
||||
#### 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/).
|
||||
|
||||
@@ -191,6 +202,10 @@ go get github.com/jteeuwen/go-bindata/...
|
||||
make all
|
||||
```
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starcharts.herokuapp.com/glauth/glauth)
|
||||
|
||||
### Support
|
||||
|
||||
Support the ongoing development of GLAuth!
|
||||
|
||||
+33
-7
@@ -105,6 +105,13 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
|
||||
}
|
||||
}
|
||||
|
||||
// Store the full bind password provided before possibly modifying
|
||||
// in the otp check
|
||||
// Generate a hash of the provided password
|
||||
hashFull := sha256.New()
|
||||
hashFull.Write([]byte(bindSimplePw))
|
||||
|
||||
// Test OTP if exists
|
||||
if len(user.OTPSecret) > 0 && !validotp {
|
||||
if len(bindSimplePw) > 6 {
|
||||
otp := bindSimplePw[len(bindSimplePw)-6:]
|
||||
@@ -114,20 +121,39 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
|
||||
}
|
||||
}
|
||||
|
||||
// finally, validate user's pw
|
||||
|
||||
// check app passwords first
|
||||
for index, appPw := range user.PassAppSHA256 {
|
||||
|
||||
if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
|
||||
log.Debug(fmt.Sprintf("Attempted to bind app pw #%d - failure as %s from %s", index, bindDN, conn.RemoteAddr().String()))
|
||||
} else {
|
||||
stats_frontend.Add("bind_successes", 1)
|
||||
log.Debug("Bind success using app pw #%d as %s from %s", index, bindDN, conn.RemoteAddr().String())
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// then check main password with the hash
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(bindSimplePw))
|
||||
|
||||
// Then ensure the OTP is valid before checking
|
||||
if !validotp {
|
||||
log.Warning(fmt.Sprintf("Bind Error: invalid token as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
log.Warning(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// finally, validate user's pw
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(bindSimplePw))
|
||||
// Now, check the hash
|
||||
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
|
||||
log.Warning(fmt.Sprintf("Bind Error: invalid credentials as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
stats_frontend.Add("bind_successes", 1)
|
||||
log.Debug("Bind success as %s from %s", bindDN, conn.RemoteAddr().String())
|
||||
log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
@@ -136,7 +162,7 @@ func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||
log.Debug("Search request as %s from %s for %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter)
|
||||
log.Debug(fmt.Sprintf("Search request as %s from %s for %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter))
|
||||
stats_frontend.Add("search_reqs", 1)
|
||||
|
||||
// validate the user is authenticated and has appropriate access
|
||||
@@ -223,7 +249,7 @@ func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn
|
||||
}
|
||||
}
|
||||
stats_frontend.Add("search_successes", 1)
|
||||
log.Debug("AP: Search OK: %s", searchReq.Filter)
|
||||
log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
|
||||
return ldap.ServerSearchResult{entries, []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ package main
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/docopt/docopt-go"
|
||||
@@ -10,8 +13,6 @@ import (
|
||||
"github.com/op/go-logging"
|
||||
"gopkg.in/amz.v1/aws"
|
||||
"gopkg.in/amz.v1/s3"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Set with buildtime vars
|
||||
@@ -61,11 +62,21 @@ type configBackend struct {
|
||||
}
|
||||
type configFrontend struct {
|
||||
AllowedBaseDNs []string // For LDAP backend only
|
||||
Listen string
|
||||
Cert string
|
||||
Key string
|
||||
Listen string
|
||||
TLS bool
|
||||
}
|
||||
type configLDAP struct {
|
||||
Enabled bool
|
||||
Listen string
|
||||
}
|
||||
type configLDAPS struct {
|
||||
Enabled bool
|
||||
Listen string
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
type configAPI struct {
|
||||
Cert string
|
||||
Enabled bool
|
||||
@@ -75,20 +86,21 @@ type configAPI struct {
|
||||
TLS bool
|
||||
}
|
||||
type configUser struct {
|
||||
Name string
|
||||
OtherGroups []int
|
||||
PassSHA256 string
|
||||
PrimaryGroup int
|
||||
SSHKeys []string
|
||||
OTPSecret string
|
||||
Yubikey string
|
||||
Disabled bool
|
||||
UnixID int
|
||||
Mail string
|
||||
LoginShell string
|
||||
GivenName string
|
||||
SN string
|
||||
Homedir string
|
||||
Name string
|
||||
OtherGroups []int
|
||||
PassSHA256 string
|
||||
PassAppSHA256 []string
|
||||
PrimaryGroup int
|
||||
SSHKeys []string
|
||||
OTPSecret string
|
||||
Yubikey string
|
||||
Disabled bool
|
||||
UnixID int
|
||||
Mail string
|
||||
LoginShell string
|
||||
GivenName string
|
||||
SN string
|
||||
Homedir string
|
||||
}
|
||||
type configGroup struct {
|
||||
Name string
|
||||
@@ -102,6 +114,8 @@ type config struct {
|
||||
YubikeyClientID string
|
||||
YubikeySecret string
|
||||
Frontend configFrontend
|
||||
LDAP configLDAP
|
||||
LDAPS configLDAPS
|
||||
Groups []configGroup
|
||||
Syslog bool
|
||||
Users []configUser
|
||||
@@ -203,26 +217,45 @@ func main() {
|
||||
s.SearchFunc("", handler)
|
||||
s.CloseFunc("", handler)
|
||||
|
||||
// start the frontend server
|
||||
if cfg.Frontend.TLS {
|
||||
log.Notice(fmt.Sprintf("Frontend LDAPS server listening on %s", cfg.Frontend.Listen))
|
||||
if err := s.ListenAndServeTLS(cfg.Frontend.Listen, cfg.Frontend.Cert, cfg.Frontend.Key); err != nil {
|
||||
log.Fatalf("LDAP Server Failed: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Notice(fmt.Sprintf("Frontend LDAP server listening on %s", cfg.Frontend.Listen))
|
||||
if err := s.ListenAndServe(cfg.Frontend.Listen); err != nil {
|
||||
log.Fatalf("LDAP Server Failed: %s", err.Error())
|
||||
if cfg.LDAP.Enabled {
|
||||
// Dont block if also starting a LDAPS server afterwards
|
||||
shouldBlock := !cfg.LDAPS.Enabled
|
||||
|
||||
if shouldBlock {
|
||||
startLDAP(&cfg.LDAP, s)
|
||||
} else {
|
||||
go startLDAP(&cfg.LDAP, s)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Enabled {
|
||||
// Always block here
|
||||
startLDAPS(&cfg.LDAPS, s)
|
||||
}
|
||||
|
||||
log.Critical("AP exit")
|
||||
}
|
||||
|
||||
func startLDAP(ldapConfig *configLDAP, server *ldap.Server) {
|
||||
log.Notice(fmt.Sprintf("LDAP server listening on %s", ldapConfig.Listen))
|
||||
if err := server.ListenAndServe(ldapConfig.Listen); err != nil {
|
||||
log.Fatalf("LDAP Server Failed: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func startLDAPS(ldapsConfig *configLDAPS, server *ldap.Server) {
|
||||
log.Notice(fmt.Sprintf("LDAPS server listening on %s", ldapsConfig.Listen))
|
||||
if err := server.ListenAndServeTLS(ldapsConfig.Listen, ldapsConfig.Cert, ldapsConfig.Key); err != nil {
|
||||
log.Fatalf("LDAP Server Failed: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// doConfig reads the cli flags and config file
|
||||
func doConfig() (*config, error) {
|
||||
cfg := config{}
|
||||
// setup defaults
|
||||
cfg.Frontend.TLS = true
|
||||
cfg.LDAP.Enabled = false
|
||||
cfg.LDAPS.Enabled = true
|
||||
|
||||
// parse the command-line args
|
||||
args, err := docopt.Parse(usage, nil, true, getVersionString(), false)
|
||||
@@ -270,6 +303,55 @@ func doConfig() (*config, error) {
|
||||
logging.SetLevel(logging.DEBUG, programName)
|
||||
log.Debug("Debugging enabled")
|
||||
}
|
||||
|
||||
if len(cfg.Frontend.Listen) > 0 && (len(cfg.LDAP.Listen) > 0 || len(cfg.LDAPS.Listen) > 0) {
|
||||
// Both old server-config and new - dont allow
|
||||
return &cfg, fmt.Errorf("Both old and new server-config in use - please remove old format ([frontend]) and migrate to new format ([ldap], [ldaps])")
|
||||
}
|
||||
|
||||
if len(cfg.Frontend.Listen) > 0 {
|
||||
// We're going with old format - parse it into new
|
||||
log.Warning("Config [frontend] is deprecated - please move to [ldap] and [ldaps] as-per documentation")
|
||||
|
||||
cfg.LDAP.Enabled = !cfg.Frontend.TLS
|
||||
cfg.LDAPS.Enabled = cfg.Frontend.TLS
|
||||
|
||||
if cfg.Frontend.TLS {
|
||||
cfg.LDAPS.Listen = cfg.Frontend.Listen
|
||||
} else {
|
||||
cfg.LDAP.Listen = cfg.Frontend.Listen
|
||||
}
|
||||
|
||||
if len(cfg.Frontend.Cert) > 0 {
|
||||
cfg.LDAPS.Cert = cfg.Frontend.Cert
|
||||
}
|
||||
if len(cfg.Frontend.Key) > 0 {
|
||||
cfg.LDAPS.Key = cfg.Frontend.Key
|
||||
}
|
||||
}
|
||||
|
||||
if !cfg.LDAP.Enabled && !cfg.LDAPS.Enabled {
|
||||
return &cfg, fmt.Errorf("No server configuration found: please provide either LDAP or LDAPS configuration")
|
||||
}
|
||||
|
||||
if cfg.LDAPS.Enabled {
|
||||
// LDAPS enabled - verify requirements (cert, key, listen)
|
||||
if len(cfg.LDAPS.Cert) == 0 || len(cfg.LDAPS.Key) == 0 {
|
||||
return &cfg, fmt.Errorf("LDAPS was enabled but no certificate or key were specified: please disable LDAPS or use the 'cert' and 'key' options")
|
||||
}
|
||||
|
||||
if len(cfg.LDAPS.Listen) == 0 {
|
||||
return &cfg, fmt.Errorf("No LDAPS bind address was specified: please disable LDAPS or use the 'listen' option")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.LDAP.Enabled {
|
||||
// LDAP enabled - verify listen
|
||||
if len(cfg.LDAP.Listen) == 0 {
|
||||
return &cfg, fmt.Errorf("No LDAP bind address was specified: please disable LDAP or use the 'listen' option")
|
||||
}
|
||||
}
|
||||
|
||||
switch cfg.Backend.Datastore {
|
||||
case "":
|
||||
cfg.Backend.Datastore = "config"
|
||||
|
||||
+12
-12
@@ -66,27 +66,27 @@ func newLdapHandler(cfg *config) Backend {
|
||||
|
||||
//
|
||||
func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
log.Debug("Bind request as %s from %s", bindDN, conn.RemoteAddr().String())
|
||||
log.Debug(fmt.Sprintf("Bind request as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
stats_frontend.Add("bind_reqs", 1)
|
||||
s, err := h.getSession(conn)
|
||||
if err != nil {
|
||||
stats_frontend.Add("bind_ldapSession_errors", 1)
|
||||
log.Debug("Bind ops error as %s from %s == %s", bindDN, conn.RemoteAddr().String(), err.Error())
|
||||
log.Debug(fmt.Sprintf("Bind ops error as %s from %s == %s", bindDN, conn.RemoteAddr().String(), err.Error()))
|
||||
return ldap.LDAPResultOperationsError, err
|
||||
}
|
||||
if err := s.ldap.Bind(bindDN, bindSimplePw); err != nil {
|
||||
stats_frontend.Add("bind_errors", 1)
|
||||
log.Debug("Bind invalid creds as %s from %s", bindDN, conn.RemoteAddr().String())
|
||||
log.Debug(fmt.Sprintf("Bind invalid creds as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
stats_frontend.Add("bind_successes", 1)
|
||||
log.Debug("Bind success as %s from %s", bindDN, conn.RemoteAddr().String())
|
||||
log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
log.Debug("Search request as %s from %s for %s", boundDN, conn.RemoteAddr().String(), searchReq.Filter)
|
||||
log.Debug(fmt.Sprintf("Search request as %s from %s for %s", boundDN, conn.RemoteAddr().String(), searchReq.Filter))
|
||||
stats_frontend.Add("search_reqs", 1)
|
||||
s, err := h.getSession(conn)
|
||||
if err != nil {
|
||||
@@ -105,24 +105,24 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
||||
searchReq.Controls,
|
||||
)
|
||||
|
||||
log.Debug("Search req to backend: %# v", pretty.Formatter(search))
|
||||
log.Debug(fmt.Sprintf("Search req to backend: %# v", pretty.Formatter(search)))
|
||||
sr, err := s.ldap.Search(search)
|
||||
log.Debug("Backend Search result: %# v", pretty.Formatter(sr))
|
||||
log.Debug(fmt.Sprintf("Backend Search result: %# v", pretty.Formatter(sr)))
|
||||
ssr := ldap.ServerSearchResult{
|
||||
Entries: sr.Entries,
|
||||
Referrals: sr.Referrals,
|
||||
Controls: sr.Controls,
|
||||
}
|
||||
log.Debug("Frontend Search result: %# v", pretty.Formatter(ssr))
|
||||
log.Debug(fmt.Sprintf("Frontend Search result: %# v", pretty.Formatter(ssr)))
|
||||
if err != nil {
|
||||
e := err.(*ldap.Error)
|
||||
log.Debug("Search Err: %# v", pretty.Formatter(err))
|
||||
log.Debug(fmt.Sprintf("Search Err: %# v", pretty.Formatter(err)))
|
||||
stats_frontend.Add("search_errors", 1)
|
||||
ssr.ResultCode = ldap.LDAPResultCode(e.ResultCode)
|
||||
return ssr, err
|
||||
}
|
||||
stats_frontend.Add("search_successes", 1)
|
||||
log.Debug("AP: Search OK: %s -> num of entries = %d\n", search.Filter, len(ssr.Entries))
|
||||
log.Debug(fmt.Sprintf("AP: Search OK: %s -> num of entries = %d\n", search.Filter, len(ssr.Entries)))
|
||||
return ssr, nil
|
||||
}
|
||||
func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
|
||||
@@ -229,7 +229,7 @@ func (h ldapHandler) ping() error {
|
||||
}
|
||||
h.lock.Unlock()
|
||||
}
|
||||
log.Debug("Server health: %# v", pretty.Formatter(h.servers))
|
||||
log.Debug(fmt.Sprintf("Server health: %# v", pretty.Formatter(h.servers)))
|
||||
b, err := json.Marshal(h.servers)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Error encoding tail data: %s", err.Error()))
|
||||
@@ -258,7 +258,7 @@ func (h ldapHandler) getBestServer() (ldapBackend, error) {
|
||||
if bestping == forever {
|
||||
return ldapBackend{}, fmt.Errorf("No healthy servers found")
|
||||
}
|
||||
log.Debug("Best server: %# v", pretty.Formatter(favorite))
|
||||
log.Debug(fmt.Sprintf("Best server: %# v", pretty.Formatter(favorite)))
|
||||
return favorite, nil
|
||||
}
|
||||
|
||||
|
||||
+29
-5
@@ -5,15 +5,33 @@
|
||||
# General configuration.
|
||||
debug = true
|
||||
#syslog = true
|
||||
|
||||
#################
|
||||
# yubikeyclientid = "yubi-api-clientid"
|
||||
# yubikeysecret = "yubi-api-secret"
|
||||
|
||||
#################
|
||||
# The frontend section controls how clients connect to the proxy.
|
||||
[frontend]
|
||||
tls = false # enable TLS for production!!
|
||||
# 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 = "localhost:3893"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
|
||||
[ldaps]
|
||||
enabled = true
|
||||
listen = "localhost:3894"
|
||||
cert = "certs/server.crt"
|
||||
key = "certs/server.key"
|
||||
|
||||
#################
|
||||
# The backend section controls the data store.
|
||||
@@ -41,7 +59,13 @@ debug = true
|
||||
loginShell = "/bin/sh"
|
||||
homeDir = "/root"
|
||||
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
|
||||
passappsha256 = ["6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a"]
|
||||
sshkeys = ["ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3UKCEllO2IZXgqNygiVb+dDLJJwVw3AJwV34t2jzR+/tUNVeJ9XddKpYQektNHsFmY93lJw5QDSbeH/mAC4KPoUM47EriINKEelRbyG4hC/ko/e2JWqEclPS9LP7GtqGmscXXo4JFkqnKw4TIRD52XI9n1syYM9Y8rJ88fjC/Lpn+01AB0paLVIfppJU35t0Ho9doHAEfEvcQA6tcm7FLJUvklAxc8WUbdziczbRV40KzDroIkXAZRjX7vXXhh/p7XBYnA0GO8oTa2VY4dTQSeDAUJSUxbzevbL0ll9Gi1uYaTDQyE5gbn2NfJSqq0OYA+3eyGtIVjFYZgi+txSuhw== rsa-key-20160209"]
|
||||
passappsha256 = [
|
||||
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
|
||||
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
|
||||
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
|
||||
]
|
||||
|
||||
[[users]]
|
||||
name = "serviceuser"
|
||||
|
||||
@@ -10,7 +10,7 @@ debug = true
|
||||
# The frontend section controls how clients connect to the proxy.
|
||||
[frontend]
|
||||
tls = false # enable TLS for production!!
|
||||
listen = "localhost:3893"
|
||||
listen = "localhost:389"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
|
||||
|
||||
+16
-1
@@ -2,12 +2,27 @@
|
||||
|
||||
# Copy config file if it doesn't exist so that the app can start
|
||||
if [ ! -f /app/config/config.cfg ] ; then
|
||||
cp /app/docker/default-config.cfg /app/config/config.cfg;
|
||||
echo "Config file not found at /app/config/config.cfg"
|
||||
echo "Copying example configuration file to run."
|
||||
cp /app/docker/default-config.cfg /app/config/config.cfg || exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo "";
|
||||
echo "Version and build information:";
|
||||
echo "";
|
||||
|
||||
# Output version string to logs
|
||||
/app/glauth --version
|
||||
|
||||
|
||||
echo "";
|
||||
echo "Starting GLauth now.";
|
||||
echo "";
|
||||
|
||||
# Run app
|
||||
/app/glauth -c /app/config/config.cfg
|
||||
|
||||
echo ""
|
||||
echo "GLauth has exited.""
|
||||
echo "Exiting."
|
||||
|
||||
@@ -87,6 +87,48 @@ memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
|
||||
dn: cn=jackdoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: jackdoe
|
||||
uid: jackdoe
|
||||
givenName: Jack
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5005
|
||||
accountStatus: active
|
||||
mail: jdoe3@example.com
|
||||
objectClass: posixAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/jackdoe
|
||||
description: jackdoe via LDAP
|
||||
gecos: jackdoe via LDAP
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
|
||||
dn: cn=sarahdoe,ou=superheros,dc=glauth,dc=com
|
||||
cn: sarahdoe
|
||||
uid: sarahdoe
|
||||
givenName: Sarah
|
||||
sn: Doe
|
||||
ou: superheros
|
||||
uidNumber: 5006
|
||||
accountStatus: active
|
||||
mail: sdoe@example.com
|
||||
objectClass: posixAccount
|
||||
loginShell: /bin/sh
|
||||
homeDirectory: /home/sarahdoe
|
||||
description: sarahdoe via LDAP
|
||||
gecos: sarahdoe via LDAP
|
||||
gidNumber: 5501
|
||||
memberOf: cn=allaccs,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=fulltime,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=mailadmin,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=superheros,ou=groups,dc=glauth,dc=com
|
||||
memberOf: cn=vpnaccess,ou=groups,dc=glauth,dc=com
|
||||
|
||||
dn: cn=serviceuser,ou=svcaccts,dc=glauth,dc=com
|
||||
cn: serviceuser
|
||||
uid: serviceuser
|
||||
|
||||
@@ -5,12 +5,16 @@ 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
|
||||
@@ -27,12 +31,16 @@ 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
|
||||
@@ -41,13 +49,17 @@ 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
|
||||
@@ -56,9 +68,13 @@ description: mailadmin via LDAP
|
||||
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
|
||||
@@ -72,7 +88,11 @@ description: fulltime via LDAP
|
||||
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
|
||||
|
||||
|
||||
@@ -4,6 +4,15 @@ export CLEANUP="$1"
|
||||
|
||||
## Main Methods
|
||||
|
||||
|
||||
# Dep check
|
||||
|
||||
command -v oathtool ;
|
||||
if [[ "$?" = "1" ]] ; then
|
||||
echo "Please install oathtool or add it to \$PATH before continuing."
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
# Get the git working directory base if travis build dir isn't set
|
||||
if [[ "$TRAVIS_BUILD_DIR" == "" ]] ; then
|
||||
export TRAVIS_BUILD_DIR="$(git rev-parse --show-toplevel)"
|
||||
@@ -25,6 +34,9 @@ echo ""
|
||||
|
||||
# Start in background, capture PID
|
||||
"$TRAVIS_BUILD_DIR/bin/glauth64" -c "$TRAVIS_BUILD_DIR/scripts/travis/test-config.cfg" &> /dev/null &
|
||||
|
||||
# Use this instead to see glauth logs while running
|
||||
# "$TRAVIS_BUILD_DIR/bin/glauth64" -c "$TRAVIS_BUILD_DIR/scripts/travis/test-config.cfg" &
|
||||
glauthPid="$!"
|
||||
|
||||
echo "Running glauth at PID=$glauthPid"
|
||||
@@ -60,9 +72,9 @@ function bindTest() {
|
||||
exitCode="$?"
|
||||
|
||||
if [[ "$exitCode" = "0" ]] ; then
|
||||
echo " - PASS : 2FA test '$4'";
|
||||
echo " - PASS : Bind test '$4'";
|
||||
else
|
||||
echo " - FAIL : 2FA test '$4'";
|
||||
echo " - FAIL : Bind test '$4'";
|
||||
FAIL="1"
|
||||
fi
|
||||
|
||||
@@ -170,6 +182,59 @@ bindTest "cn=alexdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"cn=alexdoe" \
|
||||
"OtpAlexDoe"
|
||||
|
||||
## App Password Bind Test
|
||||
|
||||
# Test the main pw
|
||||
bindTest "cn=jackdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"dogood1" \
|
||||
"cn=jackdoe" \
|
||||
"AppPwNoOtp0"
|
||||
|
||||
# App passwords on user
|
||||
bindTest "cn=jackdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw1" \
|
||||
"cn=jackdoe" \
|
||||
"AppPwNoOtp1"
|
||||
|
||||
bindTest "cn=jackdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw2" \
|
||||
"cn=jackdoe" \
|
||||
"AppPwNoOtp2"
|
||||
|
||||
bindTest "cn=jackdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw3" \
|
||||
"cn=jackdoe" \
|
||||
"AppPwNoOtp3"
|
||||
|
||||
|
||||
####
|
||||
# Test for a user who also uses OTP
|
||||
####
|
||||
otpCode="$(oathtool --totp -b -d 6 '3hnvnk4ycv44glzigd6s25j4dougs3rk')"
|
||||
|
||||
pass="dogood1"
|
||||
|
||||
# Test the main pw
|
||||
bindTest "cn=sarahdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"$pass$otpCode" \
|
||||
"cn=sarahdoe" \
|
||||
"AppPwOtp0"
|
||||
|
||||
# App passwords on user
|
||||
bindTest "cn=sarahdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw1" \
|
||||
"cn=sarahdoe" \
|
||||
"AppPwOtp1"
|
||||
|
||||
bindTest "cn=sarahdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw2" \
|
||||
"cn=sarahdoe" \
|
||||
"AppPwOtp2"
|
||||
|
||||
bindTest "cn=sarahdoe,ou=superheros,dc=glauth,dc=com" \
|
||||
"TestAppPw3" \
|
||||
"cn=sarahdoe" \
|
||||
"AppPwOtp3"
|
||||
|
||||
#############
|
||||
## Cleanup
|
||||
|
||||
@@ -10,6 +10,9 @@ debug = true
|
||||
# The frontend section controls how clients connect to the proxy.
|
||||
[frontend]
|
||||
tls = false # enable TLS for production!!
|
||||
|
||||
# Using a nonstandard port to ensure there are no
|
||||
# conflicts if running a test alongside a prod instance
|
||||
listen = "localhost:3893"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
@@ -70,6 +73,43 @@ debug = true
|
||||
passsha256 = "b1cae08a1fd8b0bd0855e48f74ba2f8548904dd5de72311e6234617a3f661e16" # ThisAloneWontWork!
|
||||
|
||||
|
||||
|
||||
# App PW test user without OTP
|
||||
[[users]]
|
||||
name = "jackdoe"
|
||||
givenname="Jack"
|
||||
sn="Doe"
|
||||
mail = "jdoe3@example.com"
|
||||
unixid = 5005
|
||||
primarygroup = 5501
|
||||
loginShell = "/bin/sh"
|
||||
otherGroups = [5505,5507]
|
||||
passsha256 = "ff522a502031e5f37054892d642c2c0f36af6c4e8f62225470d4a1a99f3c9c2a" #dogood1
|
||||
passappsha256 = [
|
||||
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
|
||||
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
|
||||
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
|
||||
]
|
||||
|
||||
# App PW test user with OTP
|
||||
[[users]]
|
||||
name = "sarahdoe"
|
||||
givenname="Sarah"
|
||||
sn="Doe"
|
||||
mail = "sdoe@example.com"
|
||||
unixid = 5006
|
||||
primarygroup = 5501
|
||||
loginShell = "/bin/sh"
|
||||
otherGroups = [5505,5507]
|
||||
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
|
||||
passsha256 = "ff522a502031e5f37054892d642c2c0f36af6c4e8f62225470d4a1a99f3c9c2a" #dogood1
|
||||
passappsha256 = [
|
||||
"c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa", # TestAppPw1
|
||||
"c9853d5f2599e90497e9f8cc671bd2022b0fb5d1bd7cfff92f079e8f8f02b8d3", # TestAppPw2
|
||||
"4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88", # TestAppPw3
|
||||
]
|
||||
|
||||
|
||||
[[users]]
|
||||
name = "serviceuser"
|
||||
unixid = 5003
|
||||
|
||||
Reference in New Issue
Block a user