mirror of
https://github.com/glauth/glauth.git
synced 2026-10-24 21:23:11 +00:00
Capabilities -- part 2 (#214) + do not dump config at startup
This commit is contained in:
committed by
GitHub
parent
8223029345
commit
0ec6f15957
+4
-1
@@ -17,4 +17,7 @@ pkg/assets/bindata.go
|
||||
# Ignoring travis CLI tool logs
|
||||
*travis.log
|
||||
|
||||
certs/
|
||||
certs/
|
||||
|
||||
# Local files
|
||||
local/
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
@@ -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")
|
||||
})
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
#################
|
||||
|
||||
@@ -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
|
||||
|
||||
#################
|
||||
|
||||
Reference in New Issue
Block a user