mirror of
https://github.com/flant/ovpn-admin.git
synced 2026-02-04 01:10:22 -08:00
added 2FA configuration and modified readme.md with required instructions
This commit is contained in:
parent
39f95e3d2c
commit
65e09ec42f
47
README.md
47
README.md
@ -15,6 +15,7 @@ Simple web UI to manage OpenVPN users, their certificates & routes in Linux. Whi
|
||||
* (optionally) Specifying/changing password for additional authorization in OpenVPN;
|
||||
* (optionally) Specifying the Kubernetes LoadBalancer if it's used in front of the OpenVPN server (to get an automatically defined `remote` in the `client.conf.tpl` template).
|
||||
* (optionally) Storing certificates and other files in Kubernetes Secrets (**Attention, this feature is experimental!**).
|
||||
* (optionally) Enabling Google Auth 2FA for each user.
|
||||
|
||||
### Screenshots
|
||||
|
||||
@ -63,7 +64,51 @@ cd ovpn-admin
|
||||
|
||||
(Please don't forget to configure all needed params in advance.)
|
||||
|
||||
### 3. Prebuilt binary
|
||||
### 3. Building from source (for enabling google-auth 2FA only)
|
||||
|
||||
***Note: This configuration is for enabling 2FA with the admin portal and must be run on the host machine. It will not work in the Docker environment due to compatibility issues with the Google Auth 2FA setup in Docker.***
|
||||
|
||||
Requirements. You need Linux with the following components installed:
|
||||
- [golang](https://golang.org/doc/install)
|
||||
- [packr2](https://github.com/gobuffalo/packr#installation)
|
||||
- [nodejs/npm](https://nodejs.org/en/download/package-manager/)
|
||||
|
||||
Commands to execute:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/palark/ovpn-admin.git
|
||||
cd ovpn-admin
|
||||
./bootstrap.sh
|
||||
./build.sh
|
||||
./ovpn-admin
|
||||
|
||||
./setup/configure.sh
|
||||
```
|
||||
|
||||
To enable the necessary authentication features, follow these steps:
|
||||
|
||||
1. Add the following lines in `templates/client.conf.tpl`:
|
||||
- auth-user-pass
|
||||
- auth-nocache
|
||||
- reneg-sec 0
|
||||
|
||||
2. Add the following line in `setup/openvpn.conf`:
|
||||
- plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn
|
||||
|
||||
3. Set the following varibales with the speicified values in `main.go`
|
||||
```
|
||||
easyrsaDirPath = /etc/openvpn/easyrsa
|
||||
indexTxtPath = /etc/openvpn/easyrsa/pki/index.txt
|
||||
authDatabase = /etc/openvpn/easyrsa/pki/users.db
|
||||
ccdDir = /etc/openvpn/ccd (if ccdEnabled set to true)
|
||||
```
|
||||
|
||||
4. Set the following varibales with the speicified values in `setup/configure.sh`
|
||||
```
|
||||
OVPN_2FA=true
|
||||
```
|
||||
|
||||
### 4. Prebuilt binary
|
||||
|
||||
You can also download and use prebuilt binaries from the [releases](https://github.com/palark/ovpn-admin/releases/latest) page — just choose a relevant tar.gz file.
|
||||
|
||||
|
||||
@ -163,6 +163,14 @@ new Vue({
|
||||
showForServerRole: ['master', 'slave'],
|
||||
showForModule: ["core"],
|
||||
},
|
||||
{
|
||||
name: 'u-download-qrcode',
|
||||
label: 'Download QR Code',
|
||||
class: 'btn-info',
|
||||
showWhenStatus: 'Active',
|
||||
showForServerRole: ['master', 'slave'],
|
||||
showForModule: ["google-auth-2fa"],
|
||||
},
|
||||
{
|
||||
name: 'u-edit-ccd',
|
||||
label: 'Edit routes',
|
||||
@ -272,6 +280,22 @@ new Vue({
|
||||
URL.revokeObjectURL(link.href)
|
||||
}).catch(console.error);
|
||||
})
|
||||
_this.$root.$on('u-download-qrcode', function () {
|
||||
const url = `/api/qr-code/${_this.username}`;
|
||||
|
||||
axios.get(url, { responseType: 'blob' })
|
||||
.then(function (response) {
|
||||
const blob = new Blob([response.data], { type: 'image/png' });
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = _this.username + ".png";
|
||||
link.click();
|
||||
|
||||
URL.revokeObjectURL(link.href);
|
||||
})
|
||||
.catch(console.error);
|
||||
});
|
||||
_this.$root.$on('u-edit-ccd', function () {
|
||||
_this.u.modalShowCcdVisible = true;
|
||||
var data = new URLSearchParams();
|
||||
|
||||
26
main.go
26
main.go
@ -74,6 +74,8 @@ var (
|
||||
logLevel = kingpin.Flag("log.level", "set log level: trace, debug, info, warn, error (default info)").Default("info").Envar("LOG_LEVEL").String()
|
||||
logFormat = kingpin.Flag("log.format", "set log format: text, json (default text)").Default("text").Envar("LOG_FORMAT").String()
|
||||
storageBackend = kingpin.Flag("storage.backend", "storage backend: filesystem, kubernetes.secrets (default filesystem)").Default("filesystem").Envar("STORAGE_BACKEND").String()
|
||||
googleAuth2FAEnabled = kingpin.Flag("auth.mfa", "enable 2FA authentication").Default("false").Envar("OVPN_2FA").Bool()
|
||||
googleAuthDir = kingpin.Flag("google-auth.path", "path to store qr-code and secret keys of users").Default("/etc/google-auth").Envar("GOOGLE_2FA_AUTH_DIR").String()
|
||||
|
||||
certsArchivePath = "/tmp/" + certsArchiveFileName
|
||||
ccdArchivePath = "/tmp/" + ccdArchiveFileName
|
||||
@ -547,6 +549,10 @@ func main() {
|
||||
ovpnAdmin.modules = append(ovpnAdmin.modules, "ccd")
|
||||
}
|
||||
|
||||
if *googleAuth2FAEnabled {
|
||||
ovpnAdmin.modules = append(ovpnAdmin.modules, "google-auth-2fa")
|
||||
}
|
||||
|
||||
if ovpnAdmin.role == "slave" {
|
||||
ovpnAdmin.syncDataFromMaster()
|
||||
go ovpnAdmin.syncWithMaster()
|
||||
@ -576,6 +582,7 @@ func main() {
|
||||
http.HandleFunc(*listenBaseUrl + "api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler)
|
||||
http.HandleFunc(*listenBaseUrl + downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler)
|
||||
http.HandleFunc(*listenBaseUrl + downloadCcdApiUrl, ovpnAdmin.downloadCcdHandler)
|
||||
http.HandleFunc(*listenBaseUrl + "api/qr-code/", downloadHandler)
|
||||
|
||||
http.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{}))
|
||||
http.HandleFunc(*listenBaseUrl + "ping", func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -586,6 +593,17 @@ func main() {
|
||||
log.Fatal(http.ListenAndServe(*listenHost+":"+*listenPort, nil))
|
||||
}
|
||||
|
||||
func downloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
username := strings.TrimPrefix(r.URL.Path, "/api/download/")
|
||||
imagePath := fmt.Sprintf("%s/%s.png", *googleAuthDir, username)
|
||||
|
||||
if _, err := os.Stat(imagePath); os.IsNotExist(err) {
|
||||
http.Error(w, "Image not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.ServeFile(w, r, imagePath)
|
||||
}
|
||||
|
||||
func CacheControlWrapper(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "max-age=2592000") // 30 days
|
||||
@ -985,7 +1003,7 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (bool, string) {
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
o := runBash(fmt.Sprintf("cd %s && %s build-client-full %s nopass 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath, username))
|
||||
o := runBash(fmt.Sprintf("cd %s && echo 'yes' | sudo %s build-client-full %s nopass 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath, username))
|
||||
log.Debug(o)
|
||||
}
|
||||
|
||||
@ -993,10 +1011,14 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (bool, string) {
|
||||
o := runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password))
|
||||
log.Debug(o)
|
||||
}
|
||||
if *googleAuth2FAEnabled {
|
||||
mfa_auth := runBash(fmt.Sprintf("sudo /etc/openvpn/google-auth.sh %s", username))
|
||||
log.Debug(mfa_auth)
|
||||
}
|
||||
|
||||
log.Infof("Certificate for user %s issued", username)
|
||||
|
||||
//oAdmin.clients = oAdmin.usersList()
|
||||
oAdmin.clients = oAdmin.usersList()
|
||||
|
||||
return true, ucErr
|
||||
}
|
||||
|
||||
@ -7,6 +7,32 @@ SERVER_CERT="${EASY_RSA_LOC}/pki/issued/server.crt"
|
||||
OVPN_SRV_NET=${OVPN_SERVER_NET:-172.16.100.0}
|
||||
OVPN_SRV_MASK=${OVPN_SERVER_MASK:-255.255.255.0}
|
||||
|
||||
OVPN_PASSWD_AUTH=${OVPN_PASSWD_AUTH:-false}
|
||||
OVPN_2FA=false
|
||||
GOOGLE_2FA_AUTH_DIR="/etc/google-auth"
|
||||
|
||||
if [ ${OVPN_2FA} = "true" ]; then
|
||||
TARGETARCH=$(dpkg --print-architecture)
|
||||
|
||||
mkdir -p $EASY_RSA_LOC
|
||||
sudo apt update -y
|
||||
|
||||
sudo apt install -y openvpn iptables
|
||||
|
||||
if [ ! -f "/usr/local/bin/easyrsa" ]; then
|
||||
sudo apt install easy-rsa
|
||||
sudo ln -sf /usr/share/easy-rsa/easyrsa /usr/local/bin/easyrsa
|
||||
fi
|
||||
|
||||
if [ ! -f "/usr/local/bin/openvpn-user" ]; then
|
||||
cd /tmp
|
||||
wget "https://github.com/pashcovich/openvpn-user/releases/download/v1.0.4/openvpn-user-linux-${TARGETARCH}.tar.gz" -O - | sudo tar xz -C /usr/local/bin
|
||||
fi
|
||||
|
||||
if [ -f "/usr/local/bin/openvpn-user-${TARGETARCH}" ]; then
|
||||
sudo ln -sf /usr/local/bin/openvpn-user-${TARGETARCH} /usr/local/bin/openvpn-user
|
||||
fi
|
||||
fi
|
||||
|
||||
cd $EASY_RSA_LOC
|
||||
|
||||
@ -39,7 +65,11 @@ if [ ! -c /dev/net/tun ]; then
|
||||
mknod /dev/net/tun c 10 200
|
||||
fi
|
||||
|
||||
if [ ${OVPN_2FA} = "true" ]; then
|
||||
cp -f /home/ubuntu/ovpn-admin/setup/openvpn.conf /etc/openvpn/openvpn.conf
|
||||
else
|
||||
cp -f /etc/openvpn/setup/openvpn.conf /etc/openvpn/openvpn.conf
|
||||
fi
|
||||
|
||||
if [ ${OVPN_PASSWD_AUTH} = "true" ]; then
|
||||
mkdir -p /etc/openvpn/scripts/
|
||||
@ -54,6 +84,61 @@ fi
|
||||
[ -d $EASY_RSA_LOC/pki ] && chmod 755 $EASY_RSA_LOC/pki
|
||||
[ -f $EASY_RSA_LOC/pki/crl.pem ] && chmod 644 $EASY_RSA_LOC/pki/crl.pem
|
||||
|
||||
if [ ${OVPN_2FA} = "true" ]; then
|
||||
if [ ! -f "/usr/local/lib/security/pam_google_authenticator.so" ]; then
|
||||
apt update && apt install -y \
|
||||
build-essential \
|
||||
linux-headers-$(uname -r) \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
cmake \
|
||||
make \
|
||||
git \
|
||||
libpam0g-dev \
|
||||
libpam-google-authenticator \
|
||||
qrencode
|
||||
|
||||
cd /tmp
|
||||
rm -rf google-authenticator-libpam
|
||||
git clone https://github.com/google/google-authenticator-libpam
|
||||
cd google-authenticator-libpam/
|
||||
./bootstrap.sh
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
rm -rf google-authenticator-libpam
|
||||
fi
|
||||
|
||||
if [ ! -f "/etc/pam.d/openvpn" ]; then
|
||||
bash -c 'cat > /etc/pam.d/openvpn <<EOF
|
||||
auth requisite /usr/local/lib/security/pam_google_authenticator.so secret=/etc/google-auth/\${USER} user=root
|
||||
account required pam_permit.so'
|
||||
fi
|
||||
|
||||
if [ ! -f "/etc/openvpn/google-auth.sh" ]; then
|
||||
sudo mkdir -p /etc/google-auth
|
||||
sudo chown -R root /etc/google-auth
|
||||
|
||||
sudo bash -c 'cat > /etc/openvpn/google-auth.sh <<EOF
|
||||
#!/bin/bash
|
||||
|
||||
CLIENT=\$1
|
||||
HOST=\$(hostname)
|
||||
R="\e[0;91m"
|
||||
G="\e[0;92m"
|
||||
W="\e[0;97m"
|
||||
B="\e[1m"
|
||||
C="\e[0m"
|
||||
|
||||
google-authenticator -t -d -f -r 3 -R 30 -W -C -s "\${GOOGLE_2FA_AUTH_DIR}/\${CLIENT}" || { echo -e "\${R}\${B}error generating QR code\${C}"; exit 1; }
|
||||
secret=\$(head -n 1 "\${GOOGLE_2FA_AUTH_DIR}/\${CLIENT}")
|
||||
qrencode -t PNG -o "\${GOOGLE_2FA_AUTH_DIR}/\${CLIENT}.png" "otpauth://totp/\${CLIENT}@\${HOST}?secret=\${secret}&issuer=openvpn" || { echo -e "\${R}\${B}Error generating PNG\${C}"; exit 1; }'
|
||||
|
||||
sudo chmod +x $GOOGLE_2FA_AUTH_DIR/google-auth.sh
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p /etc/openvpn/ccd
|
||||
|
||||
openvpn --config /etc/openvpn/openvpn.conf --client-config-dir /etc/openvpn/ccd --port 1194 --proto tcp --management 127.0.0.1 8989 --dev tun0 --server ${OVPN_SRV_NET} ${OVPN_SRV_MASK}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user