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

This commit is contained in:
Chris F Ravenscroft
2021-09-05 20:54:47 -07:00
committed by GitHub
parent 8223029345
commit 0ec6f15957
15 changed files with 213 additions and 88 deletions
+4 -1
View File
@@ -17,4 +17,7 @@ pkg/assets/bindata.go
# Ignoring travis CLI tool logs
*travis.log
certs/
certs/
# Local files
local/
+41
View File
@@ -186,6 +186,40 @@ This can be used, for instance, to inject support for Two Factor Authentication
* Example: cccjgjgkhcbb
* default = blank
### Capabilities
Introduced in 2.1.0, this feature continues improving the intrinsic security model of GLAuth.
While some level of access control is already enforced when using an LDAP backend, Capabilities are now part of the Config and Database backends.
Currently, one capability is recognized: "search" -- here is how to configure it in a Config yaml file:
```
...
[behaviors]
# Ignore all capabilities restrictions, for instance allowing every user to perform a search
IgnoreCapabilities = false
...
[[users]]
name = "hackers"
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
[[users.capabilities]]
action = "search"
object = "ou=someotherdn,dc=glauth,dc=com"
...
[[users]]
name = "serviceuser"
[[users.capabilities]]
action = "search"
object = "*"
...
```
For backward compatibility, you can set `IgnoreCapabilities` to "true"
If you are using a Database backend, check the plugins README for configuration information.
### 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
@@ -262,6 +296,13 @@ You can run `go test` to execute the tests found in `glauth_test.go` -- better,
Since some tests cover TOTP, you will first need to install `oathtool` in your environment.
You also must create a symbolink link called `glauth` to make it easy for the test framework to find the executable. For instance:
```
cd bin
ln -s glauth64 glauth
```
In order to test GLAuth against an LDAP backend, you will need docker. Run this command:
```
docker run \
-3
View File
@@ -7,7 +7,6 @@ import (
"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"
@@ -21,7 +20,6 @@ import (
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
@@ -293,7 +291,6 @@ func parseConfigFile(configFileLocation string) (*config.Config, error) {
cfg.Backends = append(cfg.Backends, cfg.Backend)
}
}
spew.Dump(cfg.Backends)
// Patch with default values where not specified
for i := range cfg.Backends {
+3 -3
View File
@@ -154,21 +154,21 @@ 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)
})
})
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")
})
+1 -1
View File
@@ -22,7 +22,7 @@ require (
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
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
)
+6
View File
@@ -51,6 +51,7 @@ type API struct {
TLS bool
}
type Behaviors struct {
IgnoreCapabilities bool
LimitFailedBinds bool
NumberOfFailedBinds int
PeriodOfFailedBinds time.Duration
@@ -58,6 +59,10 @@ type Behaviors struct {
PruneSourceTableEvery time.Duration
PruneSourcesOlderThan time.Duration
}
type Capability struct {
Action string
Object string
}
type User struct {
Name string
OtherGroups []int
@@ -66,6 +71,7 @@ type User struct {
PassAppSHA256 []string
PassAppBcrypt []string
PrimaryGroup int
Capabilities []Capability
SSHKeys []string
OTPSecret string
Yubikey string
+103 -73
View File
@@ -66,7 +66,6 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
}
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())
@@ -79,61 +78,9 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
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
}
}
user, ldapcode := l.findUser(h, bindDN, true /* checkGroup */)
if ldapcode != ldap.LDAPResultSuccess {
return ldapcode, nil
}
validotp := false
@@ -162,7 +109,7 @@ func (l LDAPOpsHelper) Bind(h LDAPOpsHandler, bindDN, bindSimplePw string, conn
// in the otp check
untouchedBindSimplePw := bindSimplePw
// Test OTP if exists
// Test OTP if is exists
if len(user.OTPSecret) > 0 && !validotp {
if len(bindSimplePw) > 6 {
otp := bindSimplePw[len(bindSimplePw)-6:]
@@ -247,23 +194,28 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
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)
anonymous := len(bindDN) < 1
var boundUser *config.User
var ldapcode ldap.LDAPResultCode
if !anonymous {
boundUser, ldapcode = l.findUser(h, bindDN, false /* checkGroup */)
if ldapcode != ldap.LDAPResultSuccess {
return ldap.ServerSearchResult{ResultCode: ldapcode}, fmt.Errorf("Search Error: Potential bypass of BindDN %s", bindDN)
}
// 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)
}
// 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 {
@@ -331,6 +283,11 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
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)
}
// return all users in the config file - the LDAP library will filter results for us
entries := []*ldap.Entry{}
filterEntity, err := ldap.GetFilterObjectClass(searchReq.Filter)
@@ -358,6 +315,84 @@ func (l LDAPOpsHelper) Search(h LDAPOpsHandler, bindDN string, searchReq ldap.Se
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
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().V(2).Info("User not found", "userprincipalname", bindDN)
return nil, ldap.LDAPResultInvalidCredentials
}
} 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 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 {
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 nil, ldap.LDAPResultInvalidCredentials
}
// 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 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().V(2).Info("Group not found", "groupname", groupName)
return nil, ldap.LDAPResultInvalidCredentials
}
}
// 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 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
@@ -395,14 +430,12 @@ func (l LDAPOpsHelper) isInTimeout(handler LDAPOpsHandler, conn net.Conn) bool {
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
@@ -420,15 +453,12 @@ func (l LDAPOpsHelper) maybePutInTimeout(handler LDAPOpsHandler, conn net.Conn,
// 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
+9 -7
View File
@@ -55,6 +55,8 @@ INSERT INTO users(name, uidnumber, primarygroup, passsha256) VALUES('hackers', 5
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');
INSERT INTO capabilities(userid, action, object) VALUES(5001, "search", "ou=superheros,dc=glauth,dc=com");
INSERT INTO capabilities(userid, action, object) VALUES(5003, "search", "*");
```
This should be equivalent to this configuration:
```text
@@ -63,12 +65,9 @@ This should be equivalent to this configuration:
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users]]
name = "hackers"
uidnumber = 5001
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
[[users]]
name = "johndoe"
@@ -80,8 +79,11 @@ This should be equivalent to this configuration:
name = "serviceuser"
mail = "serviceuser@example.com"
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
primarygroup = 5502
[[users.capabilities]]
action = "search"
object = "*"
[[users]]
name = "user4"
+17
View File
@@ -140,6 +140,23 @@ func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u
&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
if err == nil {
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
+2
View File
@@ -53,4 +53,6 @@ CREATE TABLE IF NOT EXISTS users (
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()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS capabilities (id INTEGER AUTO_INCREMENT PRIMARY KEY, userid INTEGER NOT NULL, action VARCHAR(128) NOT NULL, object VARCHAR(128) NOT NULL)")
statement.Exec()
}
+2
View File
@@ -53,4 +53,6 @@ CREATE TABLE IF NOT EXISTS users (
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()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS capabilities (id SERIAL PRIMARY KEY, userid INTEGER NOT NULL, action TEXT NOT NULL, object TEXT NOT NULL)")
statement.Exec()
}
+2
View File
@@ -53,4 +53,6 @@ CREATE TABLE IF NOT EXISTS users (
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()
statement, _ = db.Prepare("CREATE TABLE IF NOT EXISTS capabilities (id INTEGER PRIMARY KEY, userid INTEGER NOT NULL, action TEXT NOT NULL, object TEXT NOT NULL)")
statement.Exec()
}
+11
View File
@@ -45,6 +45,8 @@ debug = true
# 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
@@ -66,6 +68,9 @@ debug = true
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]]
@@ -91,6 +96,9 @@ debug = true
uidnumber = 5003
primarygroup = 5502
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
[[users.capabilities]]
action = "search"
object = "*"
# Test user showing 2 factor auth authentication
[[users]]
@@ -100,6 +108,9 @@ debug = true
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
yubikey = "vvjrcfalhlaa"
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
[[users]]
name = "uberhackers"
+6
View File
@@ -30,6 +30,9 @@ debug = true
name = "hackers"
uidnumber = 5001
primarygroup = 5501
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
# This user record shows all of the possible fields available
@@ -50,6 +53,9 @@ debug = true
name = "serviceuser"
uidnumber = 5003
primarygroup = 5502
[[users.capabilities]]
action = "search"
object = "*"
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
#################
+6
View File
@@ -30,6 +30,9 @@ debug = true
name = "hackers"
uidnumber = 5001
primarygroup = 5501
[[users.capabilities]]
action = "search"
object = "ou=superheros,dc=glauth,dc=com"
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
# This user record shows all of the possible fields available
@@ -114,6 +117,9 @@ debug = true
name = "serviceuser"
uidnumber = 5003
primarygroup = 5502
[[users.capabilities]]
action = "search"
object = "*"
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
#################