Merge 65e09ec42ffd8bef3de61ecca4a7b8fc6d156425 into 39f95e3d2c6e1f0e0fa425d9c45104d607c0e3d9

This commit is contained in:
Chirag Varshney 2025-02-24 18:20:12 +05:30 committed by GitHub
commit d4dc784891
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 181 additions and 5 deletions

View File

@ -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.

View 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
View File

@ -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
}

View File

@ -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
cp -f /etc/openvpn/setup/openvpn.conf /etc/openvpn/openvpn.conf
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}