diff --git a/.gitignore b/.gitignore index 67ea45a..05e3804 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,7 @@ pkg/assets/bindata.go # Ignoring travis CLI tool logs *travis.log -certs/ \ No newline at end of file +certs/ + +# Local files +local/ diff --git a/README.md b/README.md index e388ef5..adf6da6 100644 --- a/README.md +++ b/README.md @@ -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 \ diff --git a/glauth.go b/glauth.go index 42bedc0..c938fd2 100644 --- a/glauth.go +++ b/glauth.go @@ -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 { diff --git a/glauth_test.go b/glauth_test.go index 0072008..49fd846 100644 --- a/glauth_test.go +++ b/glauth_test.go @@ -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") }) diff --git a/go.mod b/go.mod index abe189d..6539727 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/pkg/config/config.go b/pkg/config/config.go index f6fff22..ceeebb2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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 diff --git a/pkg/handler/ldapopshelper.go b/pkg/handler/ldapopshelper.go index 62a6b52..96dcb89 100644 --- a/pkg/handler/ldapopshelper.go +++ b/pkg/handler/ldapopshelper.go @@ -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 diff --git a/pkg/plugins/README.md b/pkg/plugins/README.md index 818d2dc..4fd9740 100644 --- a/pkg/plugins/README.md +++ b/pkg/plugins/README.md @@ -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" diff --git a/pkg/plugins/basesqlhandler.go b/pkg/plugins/basesqlhandler.go index 0ffbeed..67c7730 100644 --- a/pkg/plugins/basesqlhandler.go +++ b/pkg/plugins/basesqlhandler.go @@ -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 diff --git a/pkg/plugins/mysql.go b/pkg/plugins/mysql.go index fc38065..348384e 100644 --- a/pkg/plugins/mysql.go +++ b/pkg/plugins/mysql.go @@ -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() } diff --git a/pkg/plugins/postgres.go b/pkg/plugins/postgres.go index ec5e9e8..1a081bf 100644 --- a/pkg/plugins/postgres.go +++ b/pkg/plugins/postgres.go @@ -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() } diff --git a/pkg/plugins/sqlite.go b/pkg/plugins/sqlite.go index be7135d..a2d5f75 100644 --- a/pkg/plugins/sqlite.go +++ b/pkg/plugins/sqlite.go @@ -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() } diff --git a/sample-simple.cfg b/sample-simple.cfg index ecd73b0..7b30827 100644 --- a/sample-simple.cfg +++ b/sample-simple.cfg @@ -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" diff --git a/scripts/docker/default-config.cfg b/scripts/docker/default-config.cfg index 21d41b2..2de6dd1 100644 --- a/scripts/docker/default-config.cfg +++ b/scripts/docker/default-config.cfg @@ -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 ################# diff --git a/scripts/travis/test-config.cfg b/scripts/travis/test-config.cfg index ce5dfbf..25115a5 100644 --- a/scripts/travis/test-config.cfg +++ b/scripts/travis/test-config.cfg @@ -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 #################