diff --git a/README.md b/README.md
index fdf051f..944df5f 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,11 @@ Here's a sample config wth hardcoded users and groups:
primarygroup = 5501
passsha256 = "6478579e37aff45f013e14eeb30b3cc56c72ccdc310123bcdf53e0333e3f416a" # dogood
sshkeys = [ "ssh-dss AAAAB3..." ]
+[[users]]
+ name = "uberhackers"
+ unixid = 5006
+ primarygroup = 5501
+ passbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
[[groups]]
name = "superheros"
unixid = 5501
@@ -141,6 +146,10 @@ More configuration options are documented here: https://github.com/glauth/glauth
* Specify OTP secret used to validate OTP passcode
* Example: 3hnvnk4ycv44glzigd6s25j4dougs3rk
* default = blank
+ * passappbcrypt
+ * Specify an array of app passwords which can also succesfully bind - these bypass the OTP check. Hash the same way as password.
+ * Example: ["c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa","4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88"]
+ * default = blank
* passappsha256
* Specify an array of app passwords which can also succesfully bind - these bypass the OTP check. Hash the same way as password.
* Example: ["c32255dbf6fd6b64883ec8801f793bccfa2a860f2b1ae1315cd95cdac1338efa","4939efa7c87095dacb5e7e8b8cfb3a660fa1f5edcc9108f6d7ec20ea4d6b3a88"]
@@ -153,6 +162,9 @@ More configuration options are documented here: https://github.com/glauth/glauth
### OpenSSH keys:
GLAuth can store a user's SSH authorized keys. Add one or more keys per user as shown above, then setup the goklp helper: https://github.com/appliedtrust/goklp
+### Strong Passwords
+If you are currently using sha256 passwords (`passsha256` or `passappsha256`) moving to strong, salted paswords is recommended. Simply switch to `passbcrypt` and/or `passappbcrypt` password types. Currently (2021) 212 is a reasonably good value, depending our your server's CPU.
+
### Two Factor Authentication
GLAuth can be configured to accept OTP tokens as appended to a users password. Support is added for both **TOTP tokens** (often known by it's most prominent implementation, "Google Authenticator") and **Yubikey OTP tokens**.
diff --git a/go.mod b/go.mod
index 28eaf5b..ee726f7 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.3.0
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc
+ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
)
diff --git a/go.sum b/go.sum
index eda966b..f8c72c2 100644
--- a/go.sum
+++ b/go.sum
@@ -51,14 +51,23 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc h1:ejaC8rvIvCWmsaFrvmGOxhBuMxxhBB1xRshuM98XQ7M=
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc/go.mod h1:tso14hwzqX4VbnWTNsxiL0DvMb2OwbGISFA7jDibdWc=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b h1:tdSyAzD5FHJmZAZ13d5WexAo7YJkiyGphhzFfInMnDw=
gopkg.in/amz.v3 v3.0.0-20201001071545-24fc1eceb27b/go.mod h1:cE0dCGx2UfBTjLFlzEx4EXJUmoX6BXBoX9GjKOvqha4=
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 951906f..77f5370 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -51,7 +51,9 @@ type User struct {
Name string
OtherGroups []int
PassSHA256 string
+ PassBcrypt string
PassAppSHA256 []string
+ PassAppBcrypt []string
PrimaryGroup int
SSHKeys []string
OTPSecret string
diff --git a/pkg/handler/config.go b/pkg/handler/config.go
index f48ae6f..bcf7fb3 100644
--- a/pkg/handler/config.go
+++ b/pkg/handler/config.go
@@ -19,6 +19,7 @@ import (
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/pquerna/otp/totp"
+ "golang.org/x/crypto/bcrypt"
)
var configattributematcher = regexp.MustCompile(`(?i)\((?P[a-zA-Z0-9]+)\s*=\s*(?P.*)\)`)
@@ -129,9 +130,7 @@ 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))
+ untouchedBindSimplePw := bindSimplePw
// Test OTP if exists
if len(user.OTPSecret) > 0 && !validotp {
@@ -146,21 +145,33 @@ 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)) {
- h.log.V(6).Info("Attempt to bind app pw failed", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
- } else {
- stats.Frontend.Add("bind_successes", 1)
- h.log.V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
- return ldap.LDAPResultSuccess, nil
+ if user.PassAppBcrypt != nil {
+ for index, appPw := range user.PassAppBcrypt {
+ decoded, err := hex.DecodeString(appPw)
+ if err != nil {
+ h.log.V(6).Info("invalid app credentials", "incorrect stored hash", "(omitted)")
+ } else {
+ if bcrypt.CompareHashAndPassword(decoded, []byte(untouchedBindSimplePw)) == nil {
+ stats.Frontend.Add("bind_successes", 1)
+ h.log.V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
+ return ldap.LDAPResultSuccess, nil
+ }
+ }
+ }
+ }
+ if user.PassAppSHA256 != nil {
+ hashFull := sha256.New()
+ hashFull.Write([]byte(untouchedBindSimplePw))
+ for index, appPw := range user.PassAppSHA256 {
+ if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
+ h.log.V(6).Info("Attempt to bind app pw failed", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
+ } else {
+ stats.Frontend.Add("bind_successes", 1)
+ h.log.V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
+ 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 {
@@ -168,10 +179,25 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
return ldap.LDAPResultInvalidCredentials, nil
}
- // Now, check the hash
- if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
- h.log.V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
- return ldap.LDAPResultInvalidCredentials, nil
+ // Now, check the pasword hash
+ if user.PassBcrypt != "" {
+ decoded, err := hex.DecodeString(user.PassBcrypt)
+ if err != nil {
+ h.log.V(2).Info("invalid credentials", "incorrect stored hash", "(omitted)")
+ return ldap.LDAPResultInvalidCredentials, nil
+ }
+ if bcrypt.CompareHashAndPassword(decoded, []byte(bindSimplePw)) != nil {
+ h.log.V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
+ return ldap.LDAPResultInvalidCredentials, nil
+ }
+ }
+ if user.PassSHA256 != "" {
+ hash := sha256.New()
+ hash.Write([]byte(bindSimplePw))
+ if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
+ h.log.V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
+ return ldap.LDAPResultInvalidCredentials, nil
+ }
}
stats.Frontend.Add("bind_successes", 1)
diff --git a/pkg/plugins/basesqlhandler.go b/pkg/plugins/basesqlhandler.go
index 20bb1da..a81324d 100644
--- a/pkg/plugins/basesqlhandler.go
+++ b/pkg/plugins/basesqlhandler.go
@@ -19,6 +19,7 @@ import (
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
"github.com/pquerna/otp/totp"
+ "golang.org/x/crypto/bcrypt"
)
type SqlBackend interface {
@@ -111,9 +112,9 @@ func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resul
// find the user
user := config.User{}
err = h.database.cnx.QueryRow(fmt.Sprintf(`
- SELECT u.unixid,u.primarygroup,u.passsha256,u.otpsecret,u.yubikey
+ SELECT u.unixid,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey
FROM users u WHERE lower(u.name)=%s`, h.sqlBackend.GetPrepareSymbol()), userName).Scan(
- &user.UnixID, &user.PrimaryGroup, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
+ &user.UnixID, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
if err != nil {
h.log.V(2).Info(fmt.Sprintf("Bind Error: User %s not found.", userName))
return ldap.LDAPResultInvalidCredentials, nil
@@ -157,9 +158,7 @@ func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resul
// 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))
+ untouchedBindSimplePw := bindSimplePw
// Test OTP if exists
if len(user.OTPSecret) > 0 && !validotp {
@@ -174,21 +173,33 @@ func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resul
// finally, validate user's pw
// check app passwords first
- for index, appPw := range user.PassAppSHA256 {
-
- if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
- h.log.V(2).Info(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)
- h.log.V(3).Info("Bind success using app pw #%d as %s from %s", index, bindDN, conn.RemoteAddr().String())
- return ldap.LDAPResultSuccess, nil
+ if user.PassAppBcrypt != nil {
+ for index, appPw := range user.PassAppBcrypt {
+ decoded, err := hex.DecodeString(appPw)
+ if err != nil {
+ h.log.V(6).Info("invalid app credentials", "incorrect stored hash", "(omitted)")
+ } else {
+ if bcrypt.CompareHashAndPassword(decoded, []byte(untouchedBindSimplePw)) == nil {
+ stats.Frontend.Add("bind_successes", 1)
+ h.log.V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
+ return ldap.LDAPResultSuccess, nil
+ }
+ }
+ }
+ }
+ if user.PassAppSHA256 != nil {
+ hashFull := sha256.New()
+ hashFull.Write([]byte(untouchedBindSimplePw))
+ for index, appPw := range user.PassAppSHA256 {
+ if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
+ h.log.V(2).Info(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)
+ h.log.V(3).Info("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 {
@@ -196,10 +207,25 @@ func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resul
return ldap.LDAPResultInvalidCredentials, nil
}
- // Now, check the hash
- if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
- h.log.V(2).Info(fmt.Sprintf("Bind Error: invalid credentials as %s from %s", bindDN, conn.RemoteAddr().String()))
- return ldap.LDAPResultInvalidCredentials, nil
+ // Now, check the password hash
+ if user.PassBcrypt != "" {
+ decoded, err := hex.DecodeString(user.PassBcrypt)
+ if err != nil {
+ h.log.V(2).Info("invalid credentials", "incorrect stored hash", "(omitted)")
+ return ldap.LDAPResultInvalidCredentials, nil
+ }
+ if bcrypt.CompareHashAndPassword(decoded, []byte(bindSimplePw)) != nil {
+ h.log.V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
+ return ldap.LDAPResultInvalidCredentials, nil
+ }
+ }
+ if user.PassSHA256 != "" {
+ hash := sha256.New()
+ hash.Write([]byte(bindSimplePw))
+ if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
+ h.log.V(2).Info(fmt.Sprintf("Bind Error: invalid credentials as %s from %s", bindDN, conn.RemoteAddr().String()))
+ return ldap.LDAPResultInvalidCredentials, nil
+ }
}
stats.Frontend.Add("bind_successes", 1)
@@ -249,7 +275,7 @@ func (h databaseHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
}
rows, err := h.database.cnx.Query(`
- SELECT u.name,u.unixid,u.primarygroup,u.passsha256,u.otpsecret,u.yubikey,u.othergroups,u.givenname,u.sn,u.mail,u.loginshell,u.homedirectory,u.disabled
+ SELECT u.name,u.unixid,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups,u.givenname,u.sn,u.mail,u.loginshell,u.homedirectory,u.disabled
FROM users u`)
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: Unable to retrieve data [%s]", err.Error())
@@ -260,7 +286,7 @@ func (h databaseHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
var disabled int
u := config.User{}
for rows.Next() {
- err := rows.Scan(&u.Name, &u.UnixID, &u.PrimaryGroup, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups, &u.GivenName, &u.SN, &u.Mail, &u.LoginShell, &u.Homedir, &disabled)
+ err := rows.Scan(&u.Name, &u.UnixID, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups, &u.GivenName, &u.SN, &u.Mail, &u.LoginShell, &u.Homedir, &disabled)
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: Unable to retrieve data [%s]", err.Error())
}
@@ -295,9 +321,9 @@ func (h databaseHandler) FindUser(userName string) (f bool, u config.User, err e
found := false
err = h.database.cnx.QueryRow(fmt.Sprintf(`
- SELECT u.unixid,u.primarygroup,u.passsha256,u.otpsecret,u.yubikey
+ SELECT u.unixid,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey
FROM users u WHERE lower(u.name)=%s`, h.sqlBackend.GetPrepareSymbol()), userName).Scan(
- &user.UnixID, &user.PrimaryGroup, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
+ &user.UnixID, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
if err == nil {
found = true
}
@@ -377,7 +403,7 @@ func (h databaseHandler) getGroupMembers(gid int) []string {
members := make(map[string]bool)
rows, err := h.database.cnx.Query(`
- SELECT u.name,u.unixid,u.primarygroup,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
+ SELECT u.name,u.unixid,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
FROM users u WHERE lower(u.name)=?`,
)
if err != nil {
@@ -389,7 +415,7 @@ func (h databaseHandler) getGroupMembers(gid int) []string {
var otherGroups string
u := config.User{}
for rows.Next() {
- err := rows.Scan(&u.Name, &u.UnixID, &u.PrimaryGroup, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
+ err := rows.Scan(&u.Name, &u.UnixID, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
if err != nil {
return []string{}
}
@@ -435,7 +461,7 @@ func (h databaseHandler) getGroupMembers(gid int) []string {
func (h databaseHandler) getGroupMemberIDs(gid int) []string {
members := make(map[string]bool)
rows, err := h.database.cnx.Query(`
- SELECT u.name,u.unixid,u.primarygroup,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
+ SELECT u.name,u.unixid,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
FROM users u`)
if err != nil {
// Silent fail... for now
@@ -446,7 +472,7 @@ func (h databaseHandler) getGroupMemberIDs(gid int) []string {
var otherGroups string
u := config.User{}
for rows.Next() {
- err := rows.Scan(&u.Name, &u.UnixID, &u.PrimaryGroup, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
+ err := rows.Scan(&u.Name, &u.UnixID, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
if err != nil {
return []string{}
}
diff --git a/pkg/plugins/mysql.go b/pkg/plugins/mysql.go
index bfe4a4c..c7bdf65 100644
--- a/pkg/plugins/mysql.go
+++ b/pkg/plugins/mysql.go
@@ -40,6 +40,7 @@ CREATE TABLE IF NOT EXISTS users (
homedirectory VARCHAR(64) DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 VARCHAR(64) DEFAULT '',
+ passbcrypt VARCHAR(64) DEFAULT '',
otpsecret VARCHAR(64) DEFAULT '',
yubikey VARCHAR(128) DEFAULT '')
`)
diff --git a/pkg/plugins/postgres.go b/pkg/plugins/postgres.go
index 5ab0b27..45b5954 100644
--- a/pkg/plugins/postgres.go
+++ b/pkg/plugins/postgres.go
@@ -40,6 +40,7 @@ CREATE TABLE IF NOT EXISTS users (
homedirectory TEXT DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 TEXT DEFAULT '',
+ passbcrypt TEXT DEFAULT '',
otpsecret TEXT DEFAULT '',
yubikey TEXT DEFAULT '')
`)
diff --git a/pkg/plugins/sqlite.go b/pkg/plugins/sqlite.go
index 216970e..fb210f5 100644
--- a/pkg/plugins/sqlite.go
+++ b/pkg/plugins/sqlite.go
@@ -40,6 +40,7 @@ CREATE TABLE IF NOT EXISTS users (
homedirectory TEXT DEFAULT '',
disabled SMALLINT DEFAULT 0,
passsha256 TEXT DEFAULT '',
+ passbcrypt TEXT DEFAULT '',
otpsecret TEXT DEFAULT '',
yubikey TEXT DEFAULT '')
`)
diff --git a/sample-simple.cfg b/sample-simple.cfg
index 3e7a00f..b7d8306 100644
--- a/sample-simple.cfg
+++ b/sample-simple.cfg
@@ -86,6 +86,19 @@ debug = true
otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
yubikey = "vvjrcfalhlaa"
+[[users]]
+ name = "uberhackers"
+ unixid = 5005
+ primarygroup = 5501
+ # bcrypt format: hex($2y$2^$$)
+ passappbcrypt = [
+ "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
+ ]
+ # uncomment and comment out above array to test password with otp code
+ # passappbcrypt = "243261243130244B62463462656F7265504F762E794F324957746D656541326B4B46596275674A79336A476845764B616D65446169784E41384F4432" # dogood
+ otpsecret = "3hnvnk4ycv44glzigd6s25j4dougs3rk"
+
+
#################
# The groups section contains a hardcoded list of valid users.
[[groups]]