fix passw auth; refactor type auth

This commit is contained in:
Sprait 2023-10-06 18:47:28 +00:00
parent efc44d1e5f
commit c168f36862
11 changed files with 106 additions and 63 deletions

View File

@ -12,7 +12,7 @@ var (
userIsNotActiveError = errors.New("user is not active")
passwordMismatchedError = errors.New("password mismatched")
tokenMismatchedError = errors.New("token mismatched")
checkAppError = errors.New("failed to check 2FA app")
registerAppError = errors.New("failed to register 2FA app")
checkAppError = errors.New("failed to check 2FA TOTP app")
registerAppError = errors.New("failed to register 2FA TOTP app")
authBackendDisabled = errors.New("auth backend not enabled yet")
)

View File

@ -36,8 +36,7 @@ var (
clientConfigTemplatePath = kingpin.Flag("templates.clientconfig-path", "path to custom client.conf.tpl").Default("").Envar("OVPN_TEMPLATES_CC_PATH").String()
ccdTemplatePath = kingpin.Flag("templates.ccd-path", "path to custom ccd.tpl").Default("").Envar("OVPN_TEMPLATES_CCD_PATH").String()
AuthByPassword = kingpin.Flag("auth.password", "enable additional password authentication").Default("false").Envar("OVPN_AUTH").Bool()
AuthTFA = kingpin.Flag("auth.2fa", "auth type").Default("false").Envar("OVPN_AUTH_TFA").Bool()
AuthType = kingpin.Flag("auth.type", "auth type").Default("").Envar("OVPN_AUTH").HintOptions("TOTP", "PASSWORD", "").String()
AuthDatabase = kingpin.Flag("auth.db", "database path for password authentication").Default("./easyrsa/pki/users.db").Envar("OVPN_AUTH_DB_PATH").String()
LogLevel = kingpin.Flag("log.level", "set log level: trace, debug, info, warn, error (default info)").Default("info").Envar("LOG_LEVEL").String()

View File

@ -74,7 +74,7 @@ func (oAdmin *OvpnAdmin) UserResetTFAHandler(w http.ResponseWriter, r *http.Requ
http.Error(w, err.Error(), http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "2FA reseted")
fmt.Fprintf(w, "TOTP reseted")
}
}
@ -150,7 +150,7 @@ func (oAdmin *OvpnAdmin) UserUnrevokeHandler(w http.ResponseWriter, r *http.Requ
func (oAdmin *OvpnAdmin) UserChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
log.Info(r.RemoteAddr, " ", r.RequestURI)
_ = r.ParseForm()
if *AuthByPassword {
if oAdmin.ExtraAuth {
err, msg := oAdmin.userChangePassword(r.FormValue("username"), r.FormValue("password"))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)

View File

@ -152,7 +152,7 @@ func (oAdmin *OvpnAdmin) renderClientConfig(username string) string {
conf.Key = fRead(*EasyrsaDirPath + "/pki/private/" + username + ".key")
}
conf.PasswdAuth = *AuthByPassword
conf.PasswdAuth = oAdmin.ExtraAuth
t := oAdmin.getTemplate("client.conf.tpl", "client-config", *clientConfigTemplatePath)
@ -313,8 +313,12 @@ func (oAdmin *OvpnAdmin) usersList() []OpenvpnClient {
connectedUniqUsers += 1
}
if oAdmin.isSecondFactorConfigured(ovpnClient.Identity) {
ovpnClient.SecondFactor = true
if oAdmin.ExtraAuth{
if oAdmin.isSecondFactorConfigured(ovpnClient.Identity) {
ovpnClient.SecondFactor = "enabled"
} else {
ovpnClient.SecondFactor = "disabled"
}
}
users = append(users, ovpnClient)
@ -354,7 +358,7 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (string, error) {
return err.Error(), err
}
if *AuthByPassword {
if oAdmin.ExtraAuth {
if err := validatePassword(password); err != nil {
log.Debugf("userCreate: authByPassword(): %s", err.Error())
return err.Error(), err
@ -367,7 +371,7 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (string, error) {
log.Error(err)
return err.Error(), err
}
if *AuthByPassword {
if oAdmin.ExtraAuth {
err = oAdmin.KubeClient.updatePasswordSecret(username, []byte(password))
if err != nil {
return err.Error(), err
@ -376,7 +380,7 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (string, error) {
} else {
o := runBash(fmt.Sprintf("cd %s && easyrsa build-client-full %s nopass 1>/dev/null", *EasyrsaDirPath, username))
log.Debug(o)
if *AuthByPassword {
if oAdmin.ExtraAuth {
_, err := oAdmin.OUser.CreateUser(username, password)
if err != nil {
return err.Error(), err
@ -435,14 +439,21 @@ func (oAdmin *OvpnAdmin) isSecondFactorConfigured(username string) bool {
}
return sfe
case "filesystem":
sfe, err := oAdmin.OUser.IsSecondFactorEnabled(username)
if err != nil {
return false
switch *AuthType {
case "TOTP":
sfe, err := oAdmin.OUser.IsSecondFactorEnabled(username)
if err != nil {
return false
}
return sfe
case "PASSWORD":
return true
//TODO: check if password is exist in db
}
return sfe
default:
return false
}
return false
}
func (oAdmin *OvpnAdmin) getUserSecret(username string) (string, error) {
@ -538,11 +549,11 @@ func (oAdmin *OvpnAdmin) registerUserAuthApp(username, totp string) error {
for i, u := range oAdmin.clients {
if u.Identity == username {
oAdmin.clients[i].SecondFactor = true
oAdmin.clients[i].SecondFactor = "enabled"
}
}
log.Infof("2FA configured for user %s", username)
log.Infof("TOTP configured for user %s", username)
return nil
}
return fmt.Errorf("user \"%s\" not found", username)
@ -567,7 +578,7 @@ func (oAdmin *OvpnAdmin) resetUserAuthApp(username string) error {
for i, u := range oAdmin.clients {
if u.Identity == username {
oAdmin.clients[i].SecondFactor = false
oAdmin.clients[i].SecondFactor = "disabled"
}
}
return nil
@ -587,7 +598,12 @@ func (oAdmin *OvpnAdmin) checkAuth(username, token string) error {
return authErr
}
} else {
auth, authErr = oAdmin.OUser.AuthUser(username, "", token)
switch *AuthType {
case "TOTP":
auth, authErr = oAdmin.OUser.AuthUser(username, "", token)
case "PASSWORD":
auth, authErr = oAdmin.OUser.AuthUser(username, token, "")
}
if authErr != nil {
return authErr
}
@ -625,7 +641,7 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
log.Debugln(o)
}
if *AuthByPassword {
if oAdmin.ExtraAuth {
if oAdmin.OUser.CheckUserExistent(username) {
revokeMsg, revokeErr := oAdmin.OUser.RevokedUser(username)
log.Debug(revokeMsg)
@ -693,7 +709,7 @@ func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) {
_ = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl 1>/dev/null", *EasyrsaDirPath))
if *AuthByPassword {
if oAdmin.ExtraAuth {
if oAdmin.OUser.CheckUserExistent(username) {
restoreMsg, restoreErr := oAdmin.OUser.RestoreUser(username)
log.Debug(restoreMsg)
@ -750,7 +766,7 @@ func (oAdmin *OvpnAdmin) userRotate(username, newPassword string) (error, string
log.Error(err)
}
if *AuthByPassword {
if oAdmin.ExtraAuth {
if oAdmin.OUser.CheckUserExistent(username) {
deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true)
log.Debug(deleteMsg)
@ -819,7 +835,7 @@ func (oAdmin *OvpnAdmin) userDelete(username string) (error, string) {
break
}
}
if *AuthByPassword {
if oAdmin.ExtraAuth {
if oAdmin.OUser.CheckUserExistent(username) {
deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true)
log.Debug(deleteMsg)
@ -1095,3 +1111,17 @@ func (oAdmin *OvpnAdmin) SyncWithMaster() {
oAdmin.SyncDataFromMaster()
}
}
func (oAdmin *OvpnAdmin) IsTotpAuth() bool {
if IsModuleEnabled("totpAuth", oAdmin.Modules) {
return true
}
return false
}
func (oAdmin *OvpnAdmin) IsPasswdAuth() bool {
if IsModuleEnabled("passwdAuth", oAdmin.Modules) {
return true
}
return false
}

View File

@ -24,6 +24,7 @@ type OvpnAdmin struct {
Modules []string
mgmtStatusTimeFormat string
CreateUserMutex *sync.Mutex
ExtraAuth bool
}
type OpenvpnServer struct {
@ -48,7 +49,7 @@ type OpenvpnClient struct {
RevocationDate string `json:"RevocationDate"`
ConnectionStatus string `json:"ConnectionStatus"`
Connections int `json:"Connections"`
SecondFactor bool `json:"SecondFactor"`
SecondFactor string `json:"SecondFactor,omitempty"`
}
type ccdRoute struct {

View File

@ -24,6 +24,7 @@ import (
"strings"
"time"
"unicode/utf8"
"database/sql"
)
var (
@ -555,3 +556,20 @@ func randStr(strSize int, randType string) string {
}
return string(bytes)
}
func OpenDB(path string) *sql.DB {
db, err := sql.Open("sqlite3", path)
if err != nil {
log.Fatal(err)
}
return db
}
func IsModuleEnabled(desiredModule string, listModules []string) bool {
for _, module := range listModules {
if module == desiredModule {
return true
}
}
return false
}

View File

@ -160,6 +160,7 @@ new Vue({
label: 'Download config',
class: 'btn-info',
showWhenStatus: 'Active',
Require2FA: "true",
showForServerRole: ['master', 'slave'],
showForModule: ["core"],
},
@ -353,7 +354,7 @@ new Vue({
getUserTFAData: function(data) {
let _this = this;
if (!_this.secondfactor) {
if (_this.secondfactor == 'disabled' ) {
axios.request(axios_cfg('api/user/2fa/secret', data, 'form'))
.then(function (response) {
_this.u.secret = response.data;
@ -374,15 +375,15 @@ new Vue({
_this.u.modalActionStatus = 200;
_this.u.modalRegister2faVisible = false;
_this.getUserData();
_this.secondfactor = true;
_this.secondfactor = "enabled";
_this.u.token = "";
_this.u.secret = "";
_this.$notify({title: '2FA application registered for user ' + username, type: 'success'});
_this.$notify({title: 'TOTP application registered for user ' + username, type: 'success'});
})
.catch(function(error) {
_this.u.modalActionStatus = error.response.status;
_this.u.modalActionMessage = error.response.data.message;
_this.$notify({title: 'Register 2FA application for user ' + username + ' failed!', type: 'error'});
_this.$notify({title: 'Register TOTP application for user ' + username + ' failed!', type: 'error'});
})
},
@ -396,15 +397,15 @@ new Vue({
axios.request(axios_cfg('api/user/2fa/reset', data, 'form'))
.then(function(response) {
_this.u.modalActionStatus = 200;
_this.secondfactor = false;
_this.secondfactor = "disabled";
_this.getUserTFAData(data);
_this.getUserData();
_this.$notify({title: '2FA application reset for user ' + username, type: 'success'});
_this.$notify({title: 'TOTP application reset for user ' + username, type: 'success'});
})
.catch(function(error) {
_this.u.modalActionStatus = error.response.status;
_this.u.modalActionMessage = error.response.data.message;
_this.$notify({title: 'Reset 2FA application for user ' + username + ' failed!', type: 'error'});
_this.$notify({title: 'Reset TOTP application for user ' + username + ' failed!', type: 'error'});
})
},

View File

@ -36,7 +36,10 @@
@click.left.stop="rowActionFn"
v-for="action in actions"
v-bind:class="action.class"
v-if="action.showWhenStatus == props.row.AccountStatus && action.showForServerRole.includes(serverRole) && action.showForModule.some(p=> modulesEnabled.includes(p))">
v-if="action.showWhenStatus == props.row.AccountStatus &&
( props.row.SecondFactor != 'disabled' || !(action.Require2FA) ) &&
action.showForServerRole.includes(serverRole) &&
action.showForModule.some(p=> modulesEnabled.includes(p))">
{{ action.label }}
</button>
</span>
@ -51,7 +54,7 @@
</div>
<div class="modal-body">
<input type="text" class="form-control el-square modal-el-margin" placeholder="Username [_a-zA-Z0-9\.-]" v-model="u.newUserName">
<input type="password" class="form-control el-square modal-el-margin" minlength="6" autocomplete="off" placeholder="Password [_a-zA-Z0-9\.-]" v-model="u.newUserPassword" v-if="modulesEnabled.includes('passwdAuth')">
<input type="password" class="form-control el-square modal-el-margin" minlength="6" autocomplete="off" placeholder="Password [_a-zA-Z0-9\.-]" v-model="u.newUserPassword" v-if="modulesEnabled.includes('passwdAuth')||modulesEnabled.includes('totpAuth')">
</div>
<div class="modal-footer justify-content-center" v-if="u.modalActionMessage.length > 0">
@ -232,10 +235,10 @@
<div class="modal-content">
<div class="modal-header justify-content-center">
<h4>2FA</h4>
<h4>2FA TOTP</h4>
</div>
<div class="modal-body justify-content-center" v-if="!secondfactor">
<div class="modal-body justify-content-center" v-if="secondfactor == 'disabled' ">
<div style="text-align: center">
<vue-qr
:text=u.twofaurl
@ -262,8 +265,8 @@
<input type="text" class="form-control el-square modal-el-margin" minlength="6" autocomplete="off" placeholder="Pin Code" v-model="u.token">
</div>
<div class="modal-body justify-content-center" v-if="secondfactor">
<h4>2FA already configured for user: <strong>{{ username }}</strong></h4>
<div class="modal-body justify-content-center" v-if="secondfactor == 'enabled' ">
<h4>2FA with TOTP already configured for user: <strong>{{ username }}</strong></h4>
</div>
<div class="modal-footer justify-content-center" v-if="u.modalActionMessage.length > 0">
@ -273,8 +276,8 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success el-square modal-el-margin" v-if="!secondfactor" v-on:click.stop="registerUser2faApp(username)">Register 2FA</button>
<button type="button" class="btn btn-danger el-square modal-el-margin" v-if="secondfactor" v-on:click.stop="resetUser2faApp(username)">Reset 2FA</button>
<button type="button" class="btn btn-success el-square modal-el-margin" v-if="secondfactor == 'disabled' " v-on:click.stop="registerUser2faApp(username)">Register 2FA TOTP</button>
<button type="button" class="btn btn-danger el-square modal-el-margin" v-if="secondfactor == 'enabled' " v-on:click.stop="resetUser2faApp(username)">Reset 2FA TOTP</button>
<button type="button" class="btn btn-primary el-square d-flex justify-content-sm-end modal-el-margin" v-on:click.stop="u.modalActionMessage='';u.modalRegister2faVisible=false;u.token=''">Close</button>
</div>

33
main.go
View File

@ -1,7 +1,6 @@
package main
import (
"database/sql"
"embed"
"fmt"
"io/fs"
@ -85,24 +84,17 @@ func main() {
ovpnAdmin.Modules = append(ovpnAdmin.Modules, "core")
if *backend.AuthByPassword {
db, err := sql.Open("sqlite3", *backend.AuthDatabase)
if err != nil {
kingpin.Fatalf(err.Error())
}
defer func(db *sql.DB) {
err = db.Close()
if err != nil {
kingpin.Fatalf(err.Error())
}
}(db)
ovpnAdmin.OUser.Database = db
switch *backend.AuthType {
case "TOTP":
ovpnAdmin.ExtraAuth = true
ovpnAdmin.OUser.Database = backend.OpenDB(*backend.AuthDatabase)
defer ovpnAdmin.OUser.Database.Close()
ovpnAdmin.Modules = append(ovpnAdmin.Modules, "totpAuth")
case "PASSWORD":
ovpnAdmin.ExtraAuth = true
ovpnAdmin.OUser.Database = backend.OpenDB(*backend.AuthDatabase)
defer ovpnAdmin.OUser.Database.Close()
ovpnAdmin.Modules = append(ovpnAdmin.Modules, "passwdAuth")
if *backend.AuthTFA {
ovpnAdmin.Modules = append(ovpnAdmin.Modules, "totpAuth")
}
}
if *backend.CcdEnabled {
@ -152,11 +144,10 @@ func main() {
http.HandleFunc("/api/user/ccd/apply", ovpnAdmin.UserApplyCcdHandler)
}
if *backend.AuthByPassword {
if ovpnAdmin.ExtraAuth {
http.HandleFunc("/api/user/change-password", ovpnAdmin.UserChangePasswordHandler)
http.HandleFunc("/api/auth/check", ovpnAdmin.AuthCheckHandler)
if *backend.AuthTFA {
if *backend.AuthType == "TOTP" {
http.HandleFunc("/api/user/2fa/secret", ovpnAdmin.UserGetSecretHandler)
http.HandleFunc("/api/user/2fa/register", ovpnAdmin.UserSetupTFAHandler)
http.HandleFunc("/api/user/2fa/reset", ovpnAdmin.UserResetTFAHandler)

View File

@ -7,7 +7,7 @@ auth_usr=$(head -1 $1)
auth_secret=$(tail -1 $1)
if [ $common_name = $auth_usr ]; then
curl -s --fail --data-raw 'username='${auth_usr} --data-raw 'secret='${auth_secret} localhost:8080/api/auth/check
curl -s --fail --data-raw 'username='${auth_usr} --data-raw 'token='${auth_secret} localhost:8080/api/auth/check
else
echo "$(date) Authorization for user $common_name failed"
exit 1

View File

@ -41,7 +41,7 @@ fi
cp -f /etc/openvpn/setup/openvpn.conf /etc/openvpn/openvpn.conf
if [ ${OVPN_PASSWD_AUTH} = "true" ]; then
if [ ${OVPN_AUTH} == "TOTP" ] || [ ${OVPN_AUTH} == "PASSWORD" ]; then
mkdir -p /etc/openvpn/scripts/
cp -f /etc/openvpn/setup/auth.sh /etc/openvpn/scripts/auth.sh
chmod +x /etc/openvpn/scripts/auth.sh