fix passw auth; refactor type auth
This commit is contained in:
parent
efc44d1e5f
commit
c168f36862
11 changed files with 106 additions and 63 deletions
|
@ -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")
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.ExtraAuth{
|
||||
if oAdmin.isSecondFactorConfigured(ovpnClient.Identity) {
|
||||
ovpnClient.SecondFactor = true
|
||||
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":
|
||||
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
|
||||
}
|
||||
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 {
|
||||
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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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'});
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -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
33
main.go
|
@ -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
|
||||
|
||||
ovpnAdmin.Modules = append(ovpnAdmin.Modules, "passwdAuth")
|
||||
|
||||
if *backend.AuthTFA {
|
||||
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.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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue