This commit is contained in:
Sprait 2023-11-24 14:39:34 +00:00 committed by GitHub
commit 847c33c814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 8397 additions and 875 deletions

5
.env
View File

@ -1,3 +1,4 @@
OVPN_LISTEN_BASE_URL="/"
OVPN_SERVER_NET="192.168.100.0"
OVPN_SERVER_MASK="255.255.255.0"
OVPN_NETWORK="192.168.100.0/24"
@ -6,8 +7,6 @@ OVPN_CCD_PATH="/mnt/ccd"
EASYRSA_PATH="/mnt/easyrsa"
OVPN_INDEX_PATH="/mnt/easyrsa/pki/index.txt"
OVPN_SERVER="127.0.0.1:7777:tcp"
OVPN_AUTH="true"
OVPN_AUTH_TFA="true"
OVPN_PASSWD_AUTH="true"
OVPN_AUTH="TOTP"
OVPN_AUTH_DB_PATH="/mnt/easyrsa/pki/users.db"
LOG_LEVEL="debug"

View File

@ -12,11 +12,24 @@ jobs:
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Push ovpn-admin image to Docker Hub
uses: docker/build-push-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
repository: flant/ovpn-admin
tags: latest
dockerfile: Dockerfile
- name: Push openvpn image to Docker Hub
uses: docker/build-push-action@v4
with:
tags: flant/ovpn-admin:openvpn-latest
platforms: linux/amd64,linux/arm64,linux/arm
file: Dockerfile.openvpn
push: true
- name: Push ovpn-admin image to Docker Hub
uses: docker/build-push-action@v4
with:
tags: flant/ovpn-admin:latest
platforms: linux/amd64,linux/arm64,linux/arm
file: Dockerfile
push: true

View File

@ -16,11 +16,24 @@ jobs:
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: Push ovpn-admin image to Docker Hub
uses: docker/build-push-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
repository: flant/ovpn-admin
tags: ${{ steps.get_version.outputs.VERSION }}
dockerfile: Dockerfile
- name: Push openvpn image to Docker Hub
uses: docker/build-push-action@v4
with:
tags: flant/ovpn-admin:openvpn-${{ steps.get_version.outputs.VERSION }}
platforms: linux/amd64,linux/arm64,linux/arm
file: Dockerfile.openvpn
push: true
- name: Push ovpn-admin image to Docker Hub
uses: docker/build-push-action@v4
with:
tags: flant/ovpn-admin:${{ steps.get_version.outputs.VERSION }}
platforms: linux/amd64,linux/arm64,linux/arm
file: Dockerfile
push: true

View File

@ -17,7 +17,7 @@ jobs:
- name: checkout code
uses: actions/checkout@v2
- name: build binaries
uses: wangyoucao577/go-release-action@v1.28
uses: wangyoucao577/go-release-action@v1.40
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goversion: 1.17

View File

@ -17,7 +17,7 @@ jobs:
- name: checkout code
uses: actions/checkout@v2
- name: build binaries
uses: wangyoucao577/go-release-action@v1.28
uses: wangyoucao577/go-release-action@v1.40
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goversion: 1.17

View File

@ -1,18 +1,21 @@
FROM node:16-alpine3.15 AS frontend-builder
COPY frontend/ /app
RUN cd /app && npm install && npm run build
RUN apk add --update python3 make g++ && cd /app && npm install && npm run build
FROM golang:1.17.3-buster AS backend-builder
COPY --from=frontend-builder /app/static /app/frontend/static
COPY . /app
RUN cd /app && env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -a -tags netgo -ldflags '-linkmode external -extldflags -static -s -w' -o ovpn-admin
ARG TARGETARCH
RUN cd /app && env CGO_ENABLED=1 GOOS=linux GOARCH=${TARGETARCH} go build -a -tags netgo -ldflags '-linkmode external -extldflags -static -s -w' -o ovpn-admin
FROM alpine:3.16
WORKDIR /app
ARG TARGETARCH
RUN apk add --update bash easy-rsa openssl openvpn coreutils iptables curl&& \
ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \
wget https://github.com/pashcovich/openvpn-user/releases/download/v1.0.9/openvpn-user-linux-amd64.tar.gz -O - | tar xz -C /usr/local/bin && \
wget https://github.com/pashcovich/openvpn-user/releases/download/v1.0.9/openvpn-user-linux-${TARGETARCH}.tar.gz -O - | tar xz -C /usr/local/bin && \
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
RUN if [ -f "/usr/local/bin/openvpn-user-${TARGETARCH}" ]; then ln -s /usr/local/bin/openvpn-user-${TARGETARCH} /usr/local/bin/openvpn-user; fi
COPY --from=backend-builder /app/ovpn-admin /app
COPY setup/ /etc/openvpn/setup
RUN chmod +x /etc/openvpn/setup/configure.sh

View File

@ -97,6 +97,9 @@ Flags:
--listen.port="8080" port for ovpn-admin
(or OVPN_LISTEN_PORT)
--listen.base-url="/" base URL for ovpn-admin web files
(or $OVPN_LISTEN_BASE_URL)
--role="master" server role, master or slave
(or OVPN_ROLE)

View File

@ -11,4 +11,16 @@ const (
stringDateFormat = "2006-01-02 15:04:05"
KubeNamespaceFilePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
)
secretCA = "openvpn-pki-ca"
secretServer = "openvpn-pki-server"
secretClientTmpl = "openvpn-pki-%d"
secretCRL = "openvpn-pki-crl"
secretIndexTxt = "openvpn-pki-index-txt"
secretDHandTA = "openvpn-pki-dh-and-ta"
certFileName = "tls.crt"
privKeyFileName = "tls.key"
//<year><month><day><hour><minute><second>Z
indexTxtDateFormat = "060102150405Z"
)

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

@ -3,9 +3,10 @@ package backend
import "gopkg.in/alecthomas/kingpin.v2"
var (
ListenHost = kingpin.Flag("listen.host", "host for ovpn-admin").Default("0.0.0.0").Envar("OVPN_LISTEN_HOST").String()
ListenPort = kingpin.Flag("listen.port", "port for ovpn-admin").Default("8080").Envar("OVPN_LISTEN_PORT").String()
ServerRole = kingpin.Flag("role", "server role, master or slave").Default("master").Envar("OVPN_ROLE").HintOptions("master", "slave").String()
ListenHost = kingpin.Flag("listen.host", "host for ovpn-admin").Default("0.0.0.0").Envar("OVPN_LISTEN_HOST").String()
ListenPort = kingpin.Flag("listen.port", "port for ovpn-admin").Default("8080").Envar("OVPN_LISTEN_PORT").String()
ListenBaseUrl = kingpin.Flag("listen.base-url", "base url for ovpn-admin").Default("/").Envar("OVPN_LISTEN_BASE_URL").String()
ServerRole = kingpin.Flag("role", "server role, master or slave").Default("master").Envar("OVPN_ROLE").HintOptions("master", "slave").String()
//PersonalAccess = kingpin.Flag("personalize", "personalize access for users").Default("false").Envar("OVPN_ADMIN_PERSONALIZE").Bool()
//AdminUserPassword = kingpin.Flag("admin.password", "password fom admin user").Default("admin").Envar("OVPN_ADMIN_PASSWORD").String()
@ -29,6 +30,7 @@ var (
EasyrsaDirPath = kingpin.Flag("easyrsa.path", "path to easyrsa dir").Default("./easyrsa").Envar("EASYRSA_PATH").String()
IndexTxtPath = kingpin.Flag("easyrsa.index-path", "path to easyrsa index file").Default("").Envar("OVPN_INDEX_PATH").String()
EasyrsaBinPath = kingpin.Flag("easyrsa.bin-path", "path to easyrsa script").Default("easyrsa").Envar("EASYRSA_BIN_PATH").String()
CcdEnabled = kingpin.Flag("ccd", "enable client-config-dir").Default("false").Envar("OVPN_CCD").Bool()
CcdDir = kingpin.Flag("ccd.path", "path to client-config-dir").Default("./ccd").Envar("OVPN_CCD_PATH").String()
@ -36,8 +38,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

@ -17,7 +17,7 @@ func (oAdmin *OvpnAdmin) UserListHandler(w http.ResponseWriter, r *http.Request)
}
oAdmin.clients = oAdmin.usersList()
}
usersList, _ := json.Marshal(oAdmin.clients)
fmt.Fprintf(w, "%s", usersList)
}
@ -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

@ -3,18 +3,13 @@ package backend
import (
"bytes"
"context"
"crypto/rsa"
"crypto/x509"
"errors"
"fmt"
"github.com/dgryski/dgoogauth"
"github.com/google/uuid"
// "github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"io/ioutil"
"os"
"os/exec"
"strings"
"time"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
@ -23,52 +18,11 @@ import (
"k8s.io/client-go/rest"
)
const (
secretCA = "openvpn-pki-ca"
secretServer = "openvpn-pki-server"
secretClientTmpl = "openvpn-pki-%d"
secretCRL = "openvpn-pki-crl"
secretIndexTxt = "openvpn-pki-index-txt"
secretDHandTA = "openvpn-pki-dh-and-ta"
certFileName = "tls.crt"
privKeyFileName = "tls.key"
)
//<year><month><day><hour><minute><second>Z
const indexTxtDateFormat = "060102150405Z"
var namespace = "default"
type OpenVPNPKI struct {
CAPrivKeyRSA *rsa.PrivateKey
CAPrivKeyPEM *bytes.Buffer
CACert *x509.Certificate
CACertPEM *bytes.Buffer
ServerPrivKeyRSA *rsa.PrivateKey
ServerPrivKeyPEM *bytes.Buffer
ServerCert *x509.Certificate
ServerCertPEM *bytes.Buffer
ClientCerts []ClientCert
RevokedCerts []RevokedCert
KubeClient *kubernetes.Clientset
}
type ClientCert struct {
PrivKeyRSA *rsa.PrivateKey
PrivKeyPEM *bytes.Buffer
Cert *x509.Certificate
CertPEM *bytes.Buffer
}
type RevokedCert struct {
RevokedTime time.Time `json:"revokedTime"`
CommonName string `json:"commonName"`
Cert *x509.Certificate `json:"cert"`
}
func (openVPNPKI *OpenVPNPKI) Run() (err error) {
func (openVPNPKI *OpenVPNPKI) KubeRun() (err error) {
if _, err := os.Stat(KubeNamespaceFilePath); err == nil {
file, err := ioutil.ReadFile(KubeNamespaceFilePath)
file, err := os.ReadFile(KubeNamespaceFilePath)
if err != nil {
return err
}
@ -80,7 +34,7 @@ func (openVPNPKI *OpenVPNPKI) Run() (err error) {
return
}
err = openVPNPKI.initPKI()
err = openVPNPKI.InitPKI()
if err != nil {
return
}
@ -90,18 +44,6 @@ func (openVPNPKI *OpenVPNPKI) Run() (err error) {
log.Error(err)
}
err = openVPNPKI.easyrsaGenCRL()
if err != nil {
log.Error(err)
}
if res, _ := openVPNPKI.secretCheckExists(secretDHandTA); !res {
err := openVPNPKI.secretGenTaKeyAndDHParam()
if err != nil {
log.Error(err)
}
}
err = openVPNPKI.updateFilesFromSecrets()
if err != nil {
log.Error(err)
@ -131,243 +73,243 @@ func (openVPNPKI *OpenVPNPKI) initKubeClient() (err error) {
return
}
func (openVPNPKI *OpenVPNPKI) initPKI() (err error) {
if res, _ := openVPNPKI.secretCheckExists(secretCA); res {
cert, err := openVPNPKI.secretGetClientCert(secretCA)
if err != nil {
return err
}
// func (openVPNPKI *OpenVPNPKI) initPKI() (err error) {
// if res, _ := openVPNPKI.secretCheckExists(secretCA); res {
// cert, err := openVPNPKI.secretGetClientCert(secretCA)
// if err != nil {
// return err
// }
openVPNPKI.CAPrivKeyPEM = cert.PrivKeyPEM
openVPNPKI.CAPrivKeyRSA = cert.PrivKeyRSA
openVPNPKI.CACertPEM = cert.CertPEM
openVPNPKI.CACert = cert.Cert
} else {
openVPNPKI.CAPrivKeyPEM, err = GenPrivKey()
if err != nil {
return
}
openVPNPKI.CAPrivKeyRSA, err = DecodePrivKey(openVPNPKI.CAPrivKeyPEM.Bytes())
// openVPNPKI.CAPrivKeyPEM = cert.PrivKeyPEM
// openVPNPKI.CAPrivKeyRSA = cert.PrivKeyRSA
// openVPNPKI.CACertPEM = cert.CertPEM
// openVPNPKI.CACert = cert.Cert
// } else {
// openVPNPKI.CAPrivKeyPEM, err = GenPrivKey()
// if err != nil {
// return
// }
// openVPNPKI.CAPrivKeyRSA, err = DecodePrivKey(openVPNPKI.CAPrivKeyPEM.Bytes())
openVPNPKI.CACertPEM, _ = GenCA(openVPNPKI.CAPrivKeyRSA)
openVPNPKI.CACert, err = DecodeCert(openVPNPKI.CACertPEM.Bytes())
if err != nil {
return
}
// openVPNPKI.CACertPEM, _ = GenCA(openVPNPKI.CAPrivKeyRSA)
// openVPNPKI.CACert, err = DecodeCert(openVPNPKI.CACertPEM.Bytes())
// if err != nil {
// return
// }
secretMetaData := metav1.ObjectMeta{Name: secretCA}
// secretMetaData := metav1.ObjectMeta{Name: secretCA}
secretData := map[string][]byte{
certFileName: openVPNPKI.CACertPEM.Bytes(),
privKeyFileName: openVPNPKI.CAPrivKeyPEM.Bytes(),
}
// secretData := map[string][]byte{
// certFileName: openVPNPKI.CACertPEM.Bytes(),
// privKeyFileName: openVPNPKI.CAPrivKeyPEM.Bytes(),
// }
err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS)
if err != nil {
return
}
}
// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS)
// if err != nil {
// return
// }
// }
if res, _ := openVPNPKI.secretCheckExists(secretServer); res {
cert, err := openVPNPKI.secretGetClientCert(secretServer)
if err != nil {
return err
}
// if res, _ := openVPNPKI.secretCheckExists(secretServer); res {
// cert, err := openVPNPKI.secretGetClientCert(secretServer)
// if err != nil {
// return err
// }
openVPNPKI.ServerPrivKeyPEM = cert.PrivKeyPEM
openVPNPKI.ServerPrivKeyRSA = cert.PrivKeyRSA
openVPNPKI.ServerCertPEM = cert.CertPEM
openVPNPKI.ServerCert = cert.Cert
} else {
openVPNPKI.ServerPrivKeyPEM, err = GenPrivKey()
if err != nil {
return
}
// openVPNPKI.ServerPrivKeyPEM = cert.PrivKeyPEM
// openVPNPKI.ServerPrivKeyRSA = cert.PrivKeyRSA
// openVPNPKI.ServerCertPEM = cert.CertPEM
// openVPNPKI.ServerCert = cert.Cert
// } else {
// openVPNPKI.ServerPrivKeyPEM, err = GenPrivKey()
// if err != nil {
// return
// }
openVPNPKI.ServerPrivKeyRSA, err = DecodePrivKey(openVPNPKI.ServerPrivKeyPEM.Bytes())
if err != nil {
return
}
// openVPNPKI.ServerPrivKeyRSA, err = DecodePrivKey(openVPNPKI.ServerPrivKeyPEM.Bytes())
// if err != nil {
// return
// }
openVPNPKI.ServerCertPEM, _ = GenServerCert(openVPNPKI.ServerPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, "server")
openVPNPKI.ServerCert, err = DecodeCert(openVPNPKI.ServerCertPEM.Bytes())
// openVPNPKI.ServerCertPEM, _ = GenServerCert(openVPNPKI.ServerPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, "server")
// openVPNPKI.ServerCert, err = DecodeCert(openVPNPKI.ServerCertPEM.Bytes())
secretMetaData := metav1.ObjectMeta{
Name: secretServer,
Labels: map[string]string{
"index.txt": "",
"name": "server",
"type": "serverAuth",
},
}
// secretMetaData := metav1.ObjectMeta{
// Name: secretServer,
// Labels: map[string]string{
// "index.txt": "",
// "name": "server",
// "type": "serverAuth",
// },
// }
secretData := map[string][]byte{
certFileName: openVPNPKI.ServerCertPEM.Bytes(),
privKeyFileName: openVPNPKI.ServerPrivKeyPEM.Bytes(),
}
// secretData := map[string][]byte{
// certFileName: openVPNPKI.ServerCertPEM.Bytes(),
// privKeyFileName: openVPNPKI.ServerPrivKeyPEM.Bytes(),
// }
err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS)
if err != nil {
return
}
}
// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS)
// if err != nil {
// return
// }
// }
return
}
// return
// }
func (openVPNPKI *OpenVPNPKI) indexTxtUpdate() (err error) {
secrets, err := openVPNPKI.secretsGetByLabels("index.txt=")
if err != nil {
return
}
// func (openVPNPKI *OpenVPNPKI) indexTxtUpdate() (err error) {
// secrets, err := openVPNPKI.secretsGetByLabels("index.txt=")
// if err != nil {
// return
// }
var indexTxt string
for _, secret := range secrets.Items {
certPEM := bytes.NewBuffer(secret.Data[certFileName])
log.Trace("indexTxtUpdate:" + secret.Name)
cert, err := DecodeCert(certPEM.Bytes())
if err != nil {
return nil
}
// var indexTxt string
// for _, secret := range secrets.Items {
// certPEM := bytes.NewBuffer(secret.Data[certFileName])
// log.Trace("indexTxtUpdate:" + secret.Name)
// cert, err := DecodeCert(certPEM.Bytes())
// if err != nil {
// return nil
// }
log.Trace(cert.Subject.CommonName)
// log.Trace(cert.Subject.CommonName)
if secret.Annotations["revokedAt"] == "" {
indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "V", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"])
} else if cert.NotAfter.Before(time.Now()) {
indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "E", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"])
} else {
indexTxt += fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", "R", cert.NotAfter.Format(indexTxtDateFormat), secret.Annotations["revokedAt"], fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"])
}
// if secret.Annotations["revokedAt"] == "" {
// indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "V", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"])
// } else if cert.NotAfter.Before(time.Now()) {
// indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "E", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"])
// } else {
// indexTxt += fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", "R", cert.NotAfter.Format(indexTxtDateFormat), secret.Annotations["revokedAt"], fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"])
// }
}
// }
secretMetaData := metav1.ObjectMeta{Name: secretIndexTxt}
// secretMetaData := metav1.ObjectMeta{Name: secretIndexTxt}
secretData := map[string][]byte{"index.txt": []byte(indexTxt)}
// secretData := map[string][]byte{"index.txt": []byte(indexTxt)}
if res, _ := openVPNPKI.secretCheckExists(secretIndexTxt); !res {
err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque)
} else {
err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque)
}
// if res, _ := openVPNPKI.secretCheckExists(secretIndexTxt); !res {
// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque)
// } else {
// err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque)
// }
return
}
// return
// }
func (openVPNPKI *OpenVPNPKI) updateIndexTxtOnDisk() (err error) {
secret, err := openVPNPKI.secretGetByName(secretIndexTxt)
indexTxt := secret.Data["index.txt"]
err = ioutil.WriteFile(fmt.Sprintf("%s/pki/index.txt", *EasyrsaDirPath), indexTxt, 0600)
err = fWriteRaw(fmt.Sprintf("%s/pki/index.txt", *EasyrsaDirPath), indexTxt, 0600)
return
}
func (openVPNPKI *OpenVPNPKI) easyrsaGenCRL() (err error) {
err = openVPNPKI.indexTxtUpdate()
if err != nil {
return
}
// func (openVPNPKI *OpenVPNPKI) easyrsaGenCRL() (err error) {
// err = openVPNPKI.indexTxtUpdate()
// if err != nil {
// return
// }
secrets, err := openVPNPKI.secretsGetByLabels("index.txt=,type=clientAuth")
if err != nil {
return
}
// secrets, err := openVPNPKI.secretsGetByLabels("index.txt=,type=clientAuth")
// if err != nil {
// return
// }
var revoked []*RevokedCert
// var revoked []*RevokedCert
for _, secret := range secrets.Items {
if secret.Annotations["revokedAt"] != "" {
revokedAt, err := time.Parse(indexTxtDateFormat, secret.Annotations["revokedAt"])
if err != nil {
log.Warning(err)
}
cert, err := DecodeCert(secret.Data[certFileName])
revoked = append(revoked, &RevokedCert{RevokedTime: revokedAt, Cert: cert})
}
}
// for _, secret := range secrets.Items {
// if secret.Annotations["revokedAt"] != "" {
// revokedAt, err := time.Parse(indexTxtDateFormat, secret.Annotations["revokedAt"])
// if err != nil {
// log.Warning(err)
// }
// cert, err := DecodeCert(secret.Data[certFileName])
// revoked = append(revoked, &RevokedCert{RevokedTime: revokedAt, Cert: cert})
// }
// }
crl, err := GenCRL(revoked, openVPNPKI.CACert, openVPNPKI.CAPrivKeyRSA)
if err != nil {
return
}
// crl, err := GenCRL(revoked, openVPNPKI.CACert, openVPNPKI.CAPrivKeyRSA)
// if err != nil {
// return
// }
secretMetaData := metav1.ObjectMeta{Name: secretCRL}
// secretMetaData := metav1.ObjectMeta{Name: secretCRL}
secretData := map[string][]byte{
"crl.pem": crl.Bytes(),
}
// secretData := map[string][]byte{
// "crl.pem": crl.Bytes(),
// }
//err = openVPNPKI.secretCreate(secretMetaData, secretData)
// //err = openVPNPKI.secretCreate(secretMetaData, secretData)
if res, _ := openVPNPKI.secretCheckExists(secretCRL); !res {
err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque)
} else {
err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque)
}
// if res, _ := openVPNPKI.secretCheckExists(secretCRL); !res {
// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque)
// } else {
// err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque)
// }
return
}
// return
// }
func (openVPNPKI *OpenVPNPKI) EasyrsaBuildClient(commonName string) (err error) {
// check certificate exists
_, err = openVPNPKI.secretGetByLabels("name=" + commonName)
if err == nil {
return errors.New(fmt.Sprintf("certificate for user (%s) already exists", commonName))
}
// func (openVPNPKI *OpenVPNPKI) EasyrsaBuildClient(commonName string) (err error) {
// // check certificate exists
// _, err = openVPNPKI.secretGetByLabels("name=" + commonName)
// if err == nil {
// return errors.New(fmt.Sprintf("certificate for user (%s) already exists", commonName))
// }
clientPrivKeyPEM, err := GenPrivKey()
if err != nil {
return
}
// clientPrivKeyPEM, err := GenPrivKey()
// if err != nil {
// return
// }
clientPrivKeyRSA, err := DecodePrivKey(clientPrivKeyPEM.Bytes())
if err != nil {
return
}
// clientPrivKeyRSA, err := DecodePrivKey(clientPrivKeyPEM.Bytes())
// if err != nil {
// return
// }
clientCertPEM, _ := GenClientCert(clientPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, commonName)
clientCert, err := DecodeCert(clientCertPEM.Bytes())
// clientCertPEM, _ := GenClientCert(clientPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, commonName)
// clientCert, err := DecodeCert(clientCertPEM.Bytes())
secretMetaData := metav1.ObjectMeta{
Name: fmt.Sprintf(secretClientTmpl, clientCert.SerialNumber),
Labels: map[string]string{
"index.txt": "",
"type": "clientAuth",
"name": commonName,
"app.kubernetes.io/managed-by": "ovpn-admin",
},
Annotations: map[string]string{
"commonName": commonName,
"notBefore": clientCert.NotBefore.Format(indexTxtDateFormat),
"notAfter": clientCert.NotAfter.Format(indexTxtDateFormat),
"revokedAt": "",
"serialNumber": fmt.Sprintf("%d", clientCert.SerialNumber),
},
}
// secretMetaData := metav1.ObjectMeta{
// Name: fmt.Sprintf(secretClientTmpl, clientCert.SerialNumber),
// Labels: map[string]string{
// "index.txt": "",
// "type": "clientAuth",
// "name": commonName,
// "app.kubernetes.io/managed-by": "ovpn-admin",
// },
// Annotations: map[string]string{
// "commonName": commonName,
// "notBefore": clientCert.NotBefore.Format(indexTxtDateFormat),
// "notAfter": clientCert.NotAfter.Format(indexTxtDateFormat),
// "revokedAt": "",
// "serialNumber": fmt.Sprintf("%d", clientCert.SerialNumber),
// },
// }
secretData := map[string][]byte{
certFileName: clientCertPEM.Bytes(),
privKeyFileName: clientPrivKeyPEM.Bytes(),
}
// secretData := map[string][]byte{
// certFileName: clientCertPEM.Bytes(),
// privKeyFileName: clientPrivKeyPEM.Bytes(),
// }
err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS)
if err != nil {
return
}
// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS)
// if err != nil {
// return
// }
err = openVPNPKI.indexTxtUpdate()
if err != nil {
return
}
// err = openVPNPKI.indexTxtUpdate()
// if err != nil {
// return
// }
err = openVPNPKI.updateIndexTxtOnDisk()
// err = openVPNPKI.updateIndexTxtOnDisk()
return
}
// return
// }
func (openVPNPKI *OpenVPNPKI) easyrsaGetCACert() string {
return openVPNPKI.CACertPEM.String()
}
// func (openVPNPKI *OpenVPNPKI) easyrsaGetCACert() string {
// return openVPNPKI.CACertPEM.String()
// }
func (openVPNPKI *OpenVPNPKI) EasyrsaGetClientCert(commonName string) (cert, key string) {
secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
@ -381,148 +323,148 @@ func (openVPNPKI *OpenVPNPKI) EasyrsaGetClientCert(commonName string) (cert, key
return
}
func (openVPNPKI *OpenVPNPKI) EasyrsaRevoke(commonName string) (err error) {
secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
if err != nil {
log.Error(err)
}
// func (openVPNPKI *OpenVPNPKI) EasyrsaRevoke(commonName string) (err error) {
// secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
// if err != nil {
// log.Error(err)
// }
if secret.Annotations["revokedAt"] != "" {
log.Warnf("user (%s) already revoked", commonName)
return
}
// if secret.Annotations["revokedAt"] != "" {
// log.Warnf("user (%s) already revoked", commonName)
// return
// }
secret.Annotations["revokedAt"] = time.Now().Format(indexTxtDateFormat)
// secret.Annotations["revokedAt"] = time.Now().Format(indexTxtDateFormat)
_, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
if err != nil {
return
}
// _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
// if err != nil {
// return
// }
err = openVPNPKI.indexTxtUpdate()
if err != nil {
return
}
// err = openVPNPKI.indexTxtUpdate()
// if err != nil {
// return
// }
err = openVPNPKI.updateIndexTxtOnDisk()
if err != nil {
return
}
// err = openVPNPKI.updateIndexTxtOnDisk()
// if err != nil {
// return
// }
err = openVPNPKI.easyrsaGenCRL()
if err != nil {
log.Error(err)
}
// err = openVPNPKI.easyrsaGenCRL()
// if err != nil {
// log.Error(err)
// }
err = openVPNPKI.updateCRLOnDisk()
// err = openVPNPKI.updateCRLOnDisk()
return
}
// return
// }
func (openVPNPKI *OpenVPNPKI) EasyrsaUnrevoke(commonName string) (err error) {
secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
if err != nil {
log.Error(err)
}
// func (openVPNPKI *OpenVPNPKI) EasyrsaUnrevoke(commonName string) (err error) {
// secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
// if err != nil {
// log.Error(err)
// }
secret.Annotations["revokedAt"] = ""
// secret.Annotations["revokedAt"] = ""
_, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
if err != nil {
return
}
// _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
// if err != nil {
// return
// }
err = openVPNPKI.indexTxtUpdate()
if err != nil {
return
}
// err = openVPNPKI.indexTxtUpdate()
// if err != nil {
// return
// }
err = openVPNPKI.updateIndexTxtOnDisk()
if err != nil {
return
}
// err = openVPNPKI.updateIndexTxtOnDisk()
// if err != nil {
// return
// }
err = openVPNPKI.easyrsaGenCRL()
if err != nil {
log.Error(err)
}
// err = openVPNPKI.easyrsaGenCRL()
// if err != nil {
// log.Error(err)
// }
err = openVPNPKI.updateCRLOnDisk()
// err = openVPNPKI.updateCRLOnDisk()
return
}
// return
// }
func (openVPNPKI *OpenVPNPKI) EasyrsaRotate(commonName string) (err error) {
secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
if err != nil {
log.Error(err)
}
uniqHash := strings.Replace(uuid.New().String(), "-", "", -1)
secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash
secret.Labels["name"] = "REVOKED" + commonName
secret.Labels["revokedForever"] = "true"
// func (openVPNPKI *OpenVPNPKI) EasyrsaRotate(commonName string) (err error) {
// secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
// if err != nil {
// log.Error(err)
// }
// uniqHash := strings.Replace(uuid.New().String(), "-", "", -1)
// secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash
// secret.Labels["name"] = "REVOKED" + commonName
// secret.Labels["revokedForever"] = "true"
_, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
if err != nil {
return
}
// _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
// if err != nil {
// return
// }
err = openVPNPKI.EasyrsaBuildClient(commonName)
if err != nil {
return
}
// err = openVPNPKI.EasyrsaBuildClient(commonName)
// if err != nil {
// return
// }
err = openVPNPKI.indexTxtUpdate()
if err != nil {
return
}
// err = openVPNPKI.indexTxtUpdate()
// if err != nil {
// return
// }
err = openVPNPKI.updateIndexTxtOnDisk()
if err != nil {
return
}
// err = openVPNPKI.updateIndexTxtOnDisk()
// if err != nil {
// return
// }
err = openVPNPKI.easyrsaGenCRL()
if err != nil {
log.Error(err)
}
// err = openVPNPKI.easyrsaGenCRL()
// if err != nil {
// log.Error(err)
// }
err = openVPNPKI.updateCRLOnDisk()
return
}
func (openVPNPKI *OpenVPNPKI) EasyrsaDelete(commonName string) (err error) {
secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
if err != nil {
log.Error(err)
}
uniqHash := strings.Replace(uuid.New().String(), "-", "", -1)
secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash
secret.Labels["name"] = "REVOKED-" + commonName + "-" + uniqHash
secret.Labels["revokedForever"] = "true"
// err = openVPNPKI.updateCRLOnDisk()
// return
// }
// func (openVPNPKI *OpenVPNPKI) EasyrsaDelete(commonName string) (err error) {
// secret, err := openVPNPKI.secretGetByLabels("name=" + commonName)
// if err != nil {
// log.Error(err)
// }
// uniqHash := strings.Replace(uuid.New().String(), "-", "", -1)
// secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash
// secret.Labels["name"] = "REVOKED-" + commonName + "-" + uniqHash
// secret.Labels["revokedForever"] = "true"
_, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
if err != nil {
return
}
// _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
// if err != nil {
// return
// }
err = openVPNPKI.indexTxtUpdate()
if err != nil {
return
}
// err = openVPNPKI.indexTxtUpdate()
// if err != nil {
// return
// }
err = openVPNPKI.updateIndexTxtOnDisk()
if err != nil {
return
}
// err = openVPNPKI.updateIndexTxtOnDisk()
// if err != nil {
// return
// }
err = openVPNPKI.easyrsaGenCRL()
if err != nil {
log.Error(err)
}
// err = openVPNPKI.easyrsaGenCRL()
// if err != nil {
// log.Error(err)
// }
err = openVPNPKI.updateCRLOnDisk()
return
}
// err = openVPNPKI.updateCRLOnDisk()
// return
// }
func (openVPNPKI *OpenVPNPKI) secretGetClientCert(name string) (cert ClientCert, err error) {
secret, err := openVPNPKI.secretGetByName(name)
@ -546,12 +488,12 @@ func (openVPNPKI *OpenVPNPKI) secretGetClientCert(name string) (cert ClientCert,
}
func (openVPNPKI *OpenVPNPKI) updateFilesFromSecrets() (err error) {
ca, err := openVPNPKI.secretGetClientCert(secretCA)
ca, err := openVPNPKI.getExistCert(secretCA)
if err != nil {
return
}
server, err := openVPNPKI.secretGetClientCert(secretServer)
server, err := openVPNPKI.getExistCert(secretServer)
if err != nil {
return
}
@ -568,27 +510,27 @@ func (openVPNPKI *OpenVPNPKI) updateFilesFromSecrets() (err error) {
err = os.MkdirAll(fmt.Sprintf("%s/pki/private", *EasyrsaDirPath), 0755)
}
err = ioutil.WriteFile(fmt.Sprintf("%s/pki/ca.crt", *EasyrsaDirPath), ca.CertPEM.Bytes(), 0600)
err = os.WriteFile(fmt.Sprintf("%s/pki/ca.crt", *EasyrsaDirPath), ca.CertPEM.Bytes(), 0600)
if err != nil {
return
}
err = ioutil.WriteFile(fmt.Sprintf("%s/pki/issued/server.crt", *EasyrsaDirPath), server.CertPEM.Bytes(), 0600)
err = os.WriteFile(fmt.Sprintf("%s/pki/issued/server.crt", *EasyrsaDirPath), server.CertPEM.Bytes(), 0600)
if err != nil {
return
}
err = ioutil.WriteFile(fmt.Sprintf("%s/pki/private/server.key", *EasyrsaDirPath), server.PrivKeyPEM.Bytes(), 0600)
err = os.WriteFile(fmt.Sprintf("%s/pki/private/server.key", *EasyrsaDirPath), server.PrivKeyPEM.Bytes(), 0600)
if err != nil {
return
}
err = ioutil.WriteFile(fmt.Sprintf("%s/pki/ta.key", *EasyrsaDirPath), takey, 0600)
err = os.WriteFile(fmt.Sprintf("%s/pki/ta.key", *EasyrsaDirPath), takey, 0600)
if err != nil {
return
}
err = ioutil.WriteFile(fmt.Sprintf("%s/pki/dh.pem", *EasyrsaDirPath), dhparam, 0600)
err = os.WriteFile(fmt.Sprintf("%s/pki/dh.pem", *EasyrsaDirPath), dhparam, 0600)
if err != nil {
return
}
@ -600,45 +542,45 @@ func (openVPNPKI *OpenVPNPKI) updateFilesFromSecrets() (err error) {
func (openVPNPKI *OpenVPNPKI) updateCRLOnDisk() (err error) {
secret, err := openVPNPKI.secretGetByName(secretCRL)
crl := secret.Data["crl.pem"]
err = ioutil.WriteFile(fmt.Sprintf("%s/pki/crl.pem", *EasyrsaDirPath), crl, 0644)
err = os.WriteFile(fmt.Sprintf("%s/pki/crl.pem", *EasyrsaDirPath), crl, 0644)
if err != nil {
log.Errorf("error write crl.pem:%s", err.Error())
}
return
}
func (openVPNPKI *OpenVPNPKI) secretGenTaKeyAndDHParam() (err error) {
taKeyPath := "/tmp/ta.key"
cmd := exec.Command("bash", "-c", fmt.Sprintf("/usr/sbin/openvpn --genkey --secret %s", taKeyPath))
stdout, err := cmd.CombinedOutput()
log.Info(fmt.Sprintf("/usr/sbin/openvpn --genkey --secret %s: %s", taKeyPath, string(stdout)))
if err != nil {
return
}
taKey, err := ioutil.ReadFile(taKeyPath)
// func (openVPNPKI *OpenVPNPKI) secretGenTaKeyAndDHParam() (err error) {
// taKeyPath := "/tmp/ta.key"
// cmd := exec.Command("bash", "-c", fmt.Sprintf("/usr/sbin/openvpn --genkey --secret %s", taKeyPath))
// stdout, err := cmd.CombinedOutput()
// log.Info(fmt.Sprintf("/usr/sbin/openvpn --genkey --secret %s: %s", taKeyPath, string(stdout)))
// if err != nil {
// return
// }
// taKey, err := os.ReadFile(taKeyPath)
dhparamPath := "/tmp/dh.pem"
cmd = exec.Command("bash", "-c", fmt.Sprintf("openssl dhparam -out %s 2048", dhparamPath))
_, err = cmd.CombinedOutput()
if err != nil {
return
}
dhparam, err := ioutil.ReadFile(dhparamPath)
// dhparamPath := "/tmp/dh.pem"
// cmd = exec.Command("bash", "-c", fmt.Sprintf("openssl dhparam -out %s 2048", dhparamPath))
// _, err = cmd.CombinedOutput()
// if err != nil {
// return
// }
// dhparam, err := os.ReadFile(dhparamPath)
secretMetaData := metav1.ObjectMeta{Name: secretDHandTA}
// secretMetaData := metav1.ObjectMeta{Name: secretDHandTA}
secretData := map[string][]byte{
"ta.key": taKey,
"dh.pem": dhparam,
}
// secretData := map[string][]byte{
// "ta.key": taKey,
// "dh.pem": dhparam,
// }
err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque)
if err != nil {
return
}
// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque)
// if err != nil {
// return
// }
return
}
// return
// }
// ccd
@ -690,7 +632,7 @@ func (openVPNPKI *OpenVPNPKI) updateCcdOnDisk() error {
for _, secret := range secrets.Items {
ccd := secret.Data["ccd"]
if len(ccd) > 0 {
err = ioutil.WriteFile(fmt.Sprintf("%s/%s", *CcdDir, secret.Labels["name"]), ccd, 0644)
err = os.WriteFile(fmt.Sprintf("%s/%s", *CcdDir, secret.Labels["name"]), ccd, 0644)
if err != nil {
log.Error(err)
}

View File

@ -14,7 +14,6 @@ import (
"text/template"
"time"
"github.com/google/uuid"
ou "github.com/pashcovich/openvpn-user/src"
log "github.com/sirupsen/logrus"
)
@ -152,7 +151,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)
@ -280,7 +279,7 @@ func (oAdmin *OvpnAdmin) usersList() []OpenvpnClient {
apochNow := time.Now().Unix()
for _, line := range IndexTxtParser(fRead(*IndexTxtPath)) {
if line.Identity != "server" && !strings.Contains(line.Identity, "REVOKED") {
if line.Identity != "server" && line.Identity != "ca" && !strings.Contains(line.Identity, "REVOKED") {
totalCerts += 1
ovpnClient := OpenvpnClient{Identity: line.Identity, ExpirationDate: parseDateToString(indexTxtDateLayout, line.ExpirationDate, stringDateFormat)}
switch {
@ -313,8 +312,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,40 +357,35 @@ 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
}
}
if *StorageBackend == "kubernetes.secrets" {
err := oAdmin.KubeClient.EasyrsaBuildClient(username)
if err != nil {
log.Error(err)
return err.Error(), err
}
if *AuthByPassword {
err := oAdmin.PKI.BuildKeyPairClient(username)
if err != nil {
log.Error(err)
return err.Error(), err
}
if oAdmin.ExtraAuth {
switch *StorageBackend {
case "kubernetes.secrets":
err = oAdmin.KubeClient.updatePasswordSecret(username, []byte(password))
if err != nil {
return err.Error(), err
}
}
} else {
o := runBash(fmt.Sprintf("cd %s && easyrsa build-client-full %s nopass 1>/dev/null", *EasyrsaDirPath, username))
log.Debug(o)
if *AuthByPassword {
case "filesystems":
_, err := oAdmin.OUser.CreateUser(username, password)
if err != nil {
return err.Error(), err
}
}
}
log.Infof("Certificate for user %s issued", username)
//oAdmin.clients = oAdmin.usersList()
return "", nil
}
@ -405,13 +403,13 @@ func (oAdmin *OvpnAdmin) userChangePassword(username, password string) (error, s
log.Warningf("userChangePassword: %s", err.Error())
return err, err.Error()
}
if *StorageBackend == "kubernetes.secrets" {
switch *StorageBackend {
case "kubernetes.secrets":
err := oAdmin.KubeClient.updatePasswordSecret(username, []byte(password))
if err != nil {
return err, err.Error()
}
} else {
case "filesystem":
msg, err := oAdmin.OUser.ChangeUserPassword(username, password)
if err != nil {
return err, msg
@ -435,14 +433,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 +543,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 +572,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 +592,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
}
@ -614,19 +624,13 @@ func (oAdmin *OvpnAdmin) getUserStatistic(username string) []ClientStatus {
func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
log.Infof("Revoke certificate for user %s", username)
if checkUserExist(username) {
// check certificate valid flag 'V'
if *StorageBackend == "kubernetes.secrets" {
err := oAdmin.KubeClient.EasyrsaRevoke(username)
if err != nil {
log.Error(err)
}
} else {
o := runBash(fmt.Sprintf("cd %s && echo yes | easyrsa revoke %s 1>/dev/null && easyrsa gen-crl 1>/dev/null", *EasyrsaDirPath, username))
log.Debugln(o)
err := oAdmin.PKI.CertificateRevoke(username)
if err != nil {
log.Error(err)
}
if *AuthByPassword {
if oAdmin.OUser.CheckUserExistent(username) {
if *StorageBackend == "filesystem" {
if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) {
revokeMsg, revokeErr := oAdmin.OUser.RevokedUser(username)
log.Debug(revokeMsg)
log.Debug(revokeErr)
@ -636,7 +640,6 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
}
}
crlFix()
userConnected, userConnectedTo := isUserConnected(username, oAdmin.activeClients)
log.Tracef("User %s connected: %t", username, userConnected)
if userConnected {
@ -655,67 +658,23 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) {
if checkUserExist(username) {
if *StorageBackend == "kubernetes.secrets" {
err := oAdmin.KubeClient.EasyrsaUnrevoke(username)
if err != nil {
log.Error(err)
}
} else {
// check certificate revoked flag 'R'
usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath))
for i := range usersFromIndexTxt {
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username {
if usersFromIndexTxt[i].Flag == "R" {
usersFromIndexTxt[i].Flag = "V"
usersFromIndexTxt[i].RevocationDate = ""
err := oAdmin.PKI.CertificateUnRevoke(username)
if err != nil {
log.Error(err)
}
err := fMove(fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/issued/%s.crt", *EasyrsaDirPath, username))
if err != nil {
log.Error(err)
}
err = fMove(fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/certs_by_serial/%s.pem", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber))
if err != nil {
log.Error(err)
}
err = fMove(fmt.Sprintf("%s/pki/revoked/private_by_serial/%s.key", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/private/%s.key", *EasyrsaDirPath, username))
if err != nil {
log.Error(err)
}
err = fMove(fmt.Sprintf("%s/pki/revoked/reqs_by_serial/%s.req", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/reqs/%s.req", *EasyrsaDirPath, username))
if err != nil {
log.Error(err)
}
err = fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt))
if err != nil {
log.Error(err)
}
_ = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl 1>/dev/null", *EasyrsaDirPath))
if *AuthByPassword {
if oAdmin.OUser.CheckUserExistent(username) {
restoreMsg, restoreErr := oAdmin.OUser.RestoreUser(username)
log.Debug(restoreMsg)
log.Debug(restoreErr)
if restoreErr != nil {
return restoreErr, ""
}
}
}
crlFix()
break
}
if *StorageBackend == "filesystem" {
if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) {
restoreMsg, restoreErr := oAdmin.OUser.RestoreUser(username)
log.Debug(restoreMsg)
log.Debug(restoreErr)
if restoreErr != nil {
return restoreErr, ""
}
}
err := fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt))
if err != nil {
log.Error(err)
}
}
crlFix()
oAdmin.clients = oAdmin.usersList()
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully unrevoked\"}", username)
}
@ -724,119 +683,46 @@ func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) {
func (oAdmin *OvpnAdmin) userRotate(username, newPassword string) (error, string) {
if checkUserExist(username) {
if *StorageBackend == "kubernetes.secrets" {
err := oAdmin.KubeClient.EasyrsaRotate(username)
if err != nil {
log.Error(err)
}
} else {
var oldUserIndex, newUserIndex int
var oldUserSerial string
uniqHash := strings.Replace(uuid.New().String(), "-", "", -1)
usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath))
for i := range usersFromIndexTxt {
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username {
oldUserSerial = usersFromIndexTxt[i].SerialNumber
usersFromIndexTxt[i].DistinguishedName = "/CN=REVOKED-" + username + "-" + uniqHash
oldUserIndex = i
break
}
}
err := fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt))
if err != nil {
log.Error(err)
}
if *AuthByPassword {
if oAdmin.OUser.CheckUserExistent(username) {
deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true)
log.Debug(deleteMsg)
log.Debug(deleteErr)
if deleteErr != nil {
return deleteErr, ""
}
log.Debug(deleteMsg)
}
}
userCreateMessage, userCreateError := oAdmin.userCreate(username, newPassword)
if userCreateError != nil {
usersFromIndexTxt = IndexTxtParser(fRead(*IndexTxtPath))
for i := range usersFromIndexTxt {
if usersFromIndexTxt[i].SerialNumber == oldUserSerial {
usersFromIndexTxt[i].DistinguishedName = "/CN=" + username
break
}
}
err = fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt))
if err != nil {
log.Error(err)
}
return fmt.Errorf("error rotaing user due: %s", userCreateMessage), userCreateMessage
}
usersFromIndexTxt = IndexTxtParser(fRead(*IndexTxtPath))
for i := range usersFromIndexTxt {
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username {
newUserIndex = i
}
if usersFromIndexTxt[i].SerialNumber == oldUserSerial {
oldUserIndex = i
}
}
usersFromIndexTxt[oldUserIndex], usersFromIndexTxt[newUserIndex] = usersFromIndexTxt[newUserIndex], usersFromIndexTxt[oldUserIndex]
err = fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt))
if err != nil {
log.Error(err)
}
_ = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl 1>/dev/null", *EasyrsaDirPath))
err := oAdmin.PKI.CertificateRotate(username)
if err != nil {
log.Error(err)
}
if *StorageBackend == "filesystem" {
if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) {
deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true)
log.Debug(deleteMsg)
log.Debug(deleteErr)
if deleteErr != nil {
return deleteErr, ""
}
log.Debug(deleteMsg)
}
}
crlFix()
oAdmin.clients = oAdmin.usersList()
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully rotated\"}", username)
}
return fmt.Errorf("user \"%s\" not found", username), fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username)
}
}
func (oAdmin *OvpnAdmin) userDelete(username string) (error, string) {
if checkUserExist(username) {
if *StorageBackend == "kubernetes.secrets" {
err := oAdmin.KubeClient.EasyrsaDelete(username)
if err != nil {
log.Error(err)
}
} else {
uniqHash := strings.Replace(uuid.New().String(), "-", "", -1)
usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath))
for i := range usersFromIndexTxt {
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username {
usersFromIndexTxt[i].DistinguishedName = "/CN=REVOKED-" + username + "-" + uniqHash
break
}
}
if *AuthByPassword {
if oAdmin.OUser.CheckUserExistent(username) {
deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true)
log.Debug(deleteMsg)
log.Debug(deleteErr)
if deleteErr != nil {
log.Debug(deleteErr)
return deleteErr, ""
}
}
}
err := fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt))
if err != nil {
log.Error(err)
}
_ = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl 1>/dev/null ", *EasyrsaDirPath))
err := oAdmin.PKI.CertificateDelAfterRevoke(username)
if err != nil {
log.Error(err)
}
if *StorageBackend == "filesystem" {
if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) {
deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true)
log.Debug(deleteMsg)
log.Debug(deleteErr)
if deleteErr != nil {
return deleteErr, ""
}
log.Debug(deleteMsg)
}
}
crlFix()
oAdmin.clients = oAdmin.usersList()
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully deleted\"}", username)
}
@ -1095,3 +981,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

@ -3,7 +3,12 @@ package backend
import (
"io/fs"
"sync"
"bytes"
"time"
"crypto/rsa"
"crypto/x509"
"k8s.io/client-go/kubernetes"
"github.com/pashcovich/openvpn-user/src"
"github.com/prometheus/client_golang/prometheus"
)
@ -19,11 +24,13 @@ type OvpnAdmin struct {
PromRegistry *prometheus.Registry
OUser *src.OpenvpnUser
KubeClient *OpenVPNPKI
PKI *OpenVPNPKI
MgmtInterfaces map[string]string
Templates fs.FS
Modules []string
mgmtStatusTimeFormat string
CreateUserMutex *sync.Mutex
ExtraAuth bool
}
type OpenvpnServer struct {
@ -48,7 +55,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 {
@ -85,3 +92,32 @@ type ClientStatus struct {
LastRefFormatted string
ConnectedTo string
}
type OpenVPNPKI struct {
CAPrivKeyRSA *rsa.PrivateKey
CAPrivKeyPEM *bytes.Buffer
CACert *x509.Certificate
CACertPEM *bytes.Buffer
ServerPrivKeyRSA *rsa.PrivateKey
ServerPrivKeyPEM *bytes.Buffer
ServerCert *x509.Certificate
ServerCertPEM *bytes.Buffer
TaKey *bytes.Buffer
DhParam *bytes.Buffer
ClientCerts []ClientCert
RevokedCerts []RevokedCert
KubeClient *kubernetes.Clientset
}
type ClientCert struct {
PrivKeyRSA *rsa.PrivateKey
PrivKeyPEM *bytes.Buffer
Cert *x509.Certificate
CertPEM *bytes.Buffer
}
type RevokedCert struct {
RevokedTime time.Time `json:"revokedTime"`
CommonName string `json:"commonName"`
Cert *x509.Certificate `json:"cert"`
}

1089
backend/pki.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,13 @@ import (
"compress/gzip"
"context"
"crypto/x509"
"database/sql"
"encoding/pem"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"io/fs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
@ -208,7 +209,7 @@ func getOvpnServerHostsFromKubeApi() ([]OpenvpnServer, error) {
func getOvpnCaCertExpireDate() time.Time {
caCertPath := *EasyrsaDirPath + "/pki/ca.crt"
caCert, err := ioutil.ReadFile(caCertPath)
caCert, err := os.ReadFile(caCertPath)
if err != nil {
log.Errorf("error read file %s: %s", caCertPath, err.Error())
}
@ -277,13 +278,27 @@ func fExist(path string) bool {
}
func fRead(path string) string {
content, err := ioutil.ReadFile(path)
content := fReadRaw(path)
return string(content)
}
func fReadRaw(path string) []byte {
content, err := os.ReadFile(path)
if err != nil {
log.Warning(err)
return ""
return nil
}
return string(content)
return content
}
func fReadDir(path string) []fs.DirEntry {
files, err := os.ReadDir(path)
if err != nil {
log.Warning(err)
}
return files
}
func fCreate(path string) error {
@ -300,7 +315,15 @@ func fCreate(path string) error {
}
func fWrite(path, content string) error {
err := ioutil.WriteFile(path, []byte(content), 0644)
err := fWriteRaw(path, []byte(content), 0644)
if err != nil {
log.Fatal(err)
}
return nil
}
func fWriteRaw(path string, rawContent []byte, perm fs.FileMode) error {
err := os.WriteFile(path, []byte(rawContent), perm)
if err != nil {
log.Fatal(err)
}
@ -394,7 +417,7 @@ func fDownload(path, url string, basicAuth bool) error {
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
@ -555,3 +578,34 @@ 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
}
func GetSerialNumberByUser(username string) string {
var serialNumberInTxt string
usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath))
for i := range usersFromIndexTxt {
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username {
if usersFromIndexTxt[i].Flag == "R" {
serialNumberInTxt = usersFromIndexTxt[i].SerialNumber
break
}
}
}
return serialNumberInTxt
}

View File

@ -31,7 +31,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -90,7 +90,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -149,7 +149,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -206,7 +206,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -263,7 +263,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -305,7 +305,7 @@
},
"textMode": "auto"
},
"pluginVersion": "8.5.2",
"pluginVersion": "8.5.13",
"targets": [
{
"expr": "ovpn_clients_expired",
@ -320,7 +320,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -381,7 +381,7 @@
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -469,7 +469,7 @@
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -557,7 +557,7 @@
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -647,7 +647,7 @@
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -733,7 +733,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"description": "value show last connection check time",
"fieldConfig": {
@ -794,7 +794,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"description": "value shows when connection was started",
"fieldConfig": {
@ -855,7 +855,7 @@
{
"datasource": {
"type": "prometheus",
"uid": "P0D6E4079E36703EB"
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
@ -928,7 +928,26 @@
"style": "dark",
"tags": [],
"templating": {
"list": []
"list": [
{
"current": {
"selected": false,
"text": "default",
"value": "default"
},
"hide": 0,
"includeAll": false,
"multi": false,
"label": "Prometheus",
"name": "ds_prometheus",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-15m",
@ -952,4 +971,4 @@
"uid": "Z7qmFI0Gk",
"version": 1,
"weekStart": ""
}
}

View File

@ -9,7 +9,8 @@ services:
environment:
OVPN_SERVER_NET: ${OVPN_SERVER_NET}
OVPN_SERVER_MASK: ${OVPN_SERVER_MASK}
OVPN_PASSWD_AUTH: ${OVPN_PASSWD_AUTH}
OVPN_LISTEN_BASE_URL: ${OVPN_LISTEN_BASE_URL}
OVPN_AUTH: ${OVPN_AUTH}
cap_add:
- NET_ADMIN
ports:
@ -26,12 +27,12 @@ services:
environment:
OVPN_NETWORK: ${OVPN_NETWORK}
OVPN_CCD: ${OVPN_CCD}
OVPN_CCD_PATH: ${OVPN_CCD_PATH
OVPN_CCD_PATH: ${OVPN_CCD_PATH}
EASYRSA_PATH: ${EASYRSA_PATH}
OVPN_SERVER: ${OVPN_SERVER}
OVPN_INDEX_PATH: ${OVPN_INDEX_PATH}
OVPN_LISTEN_BASE_URL: ${OVPN_LISTEN_BASE_URL}
OVPN_AUTH: ${OVPN_AUTH}
OVPN_AUTH_TFA: ${OVPN_AUTH_TFA}
OVPN_AUTH_DB_PATH: ${OVPN_AUTH_DB_PATH}
LOG_LEVEL: ${LOG_LEVEL}
network_mode: service:openvpn

File diff suppressed because it is too large Load Diff

View File

@ -42,7 +42,7 @@
"terser-webpack-plugin": "^5.3.0",
"vue-loader": "^17.0.0",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.65.0",
"webpack": "^5.76.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2"
}

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>

10
go.mod
View File

@ -35,17 +35,17 @@ require (
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
k8s.io/klog/v2 v2.40.1 // indirect
k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26 // indirect
k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 // indirect

15
go.sum
View File

@ -404,8 +404,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -489,13 +490,15 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -505,8 +508,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -693,8 +697,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

92
main.go
View File

@ -1,7 +1,6 @@
package main
import (
"database/sql"
"embed"
"fmt"
"io/fs"
@ -51,9 +50,16 @@ func main() {
ovpnAdmin := new(backend.OvpnAdmin)
ovpnAdmin.OUser = new(ou.OpenvpnUser)
ovpnAdmin.PKI = new(backend.OpenVPNPKI)
err := ovpnAdmin.PKI.InitPKI()
if err != nil {
log.Error(err)
}
if *backend.StorageBackend == "kubernetes.secrets" {
// TODO: Check
ovpnAdmin.KubeClient = new(backend.OpenVPNPKI)
err := ovpnAdmin.KubeClient.Run()
err := ovpnAdmin.KubeClient.KubeRun()
if err != nil {
log.Error(err)
}
@ -85,24 +91,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 {
@ -134,46 +133,47 @@ func main() {
go ovpnAdmin.UpdateState()
http.Handle("/", static)
http.HandleFunc("/api/server/settings", ovpnAdmin.ServerSettingsHandler)
http.HandleFunc("/api/users/list", ovpnAdmin.UserListHandler)
http.HandleFunc("/api/user/create", ovpnAdmin.UserCreateHandler)
http.HandleFunc("/api/user/rotate", ovpnAdmin.UserRotateHandler)
http.HandleFunc("/api/user/delete", ovpnAdmin.UserDeleteHandler)
http.HandleFunc("/api/user/revoke", ovpnAdmin.UserRevokeHandler)
http.HandleFunc("/api/user/unrevoke", ovpnAdmin.UserUnrevokeHandler)
http.HandleFunc("/api/user/config/show", ovpnAdmin.UserShowConfigHandler)
listenBaseUrl := *backend.ListenBaseUrl
http.HandleFunc("/api/user/disconnect", ovpnAdmin.UserDisconnectHandler)
http.HandleFunc("/api/user/statistic", ovpnAdmin.UserStatisticHandler)
http.Handle(listenBaseUrl, http.StripPrefix(strings.TrimRight(listenBaseUrl, "/"), static))
http.HandleFunc(listenBaseUrl + "api/server/settings", ovpnAdmin.ServerSettingsHandler)
http.HandleFunc(listenBaseUrl + "api/users/list", ovpnAdmin.UserListHandler)
http.HandleFunc(listenBaseUrl + "api/user/create", ovpnAdmin.UserCreateHandler)
http.HandleFunc(listenBaseUrl + "api/user/rotate", ovpnAdmin.UserRotateHandler)
http.HandleFunc(listenBaseUrl + "api/user/delete", ovpnAdmin.UserDeleteHandler)
http.HandleFunc(listenBaseUrl + "api/user/revoke", ovpnAdmin.UserRevokeHandler)
http.HandleFunc(listenBaseUrl + "api/user/unrevoke", ovpnAdmin.UserUnrevokeHandler)
http.HandleFunc(listenBaseUrl + "api/user/config/show", ovpnAdmin.UserShowConfigHandler)
http.HandleFunc(listenBaseUrl + "api/user/disconnect", ovpnAdmin.UserDisconnectHandler)
http.HandleFunc(listenBaseUrl + "api/user/statistic", ovpnAdmin.UserStatisticHandler)
if *backend.CcdEnabled {
http.HandleFunc("/api/user/ccd", ovpnAdmin.UserShowCcdHandler)
http.HandleFunc("/api/user/ccd/apply", ovpnAdmin.UserApplyCcdHandler)
http.HandleFunc(listenBaseUrl + "api/user/ccd", ovpnAdmin.UserShowCcdHandler)
http.HandleFunc(listenBaseUrl + "api/user/ccd/apply", ovpnAdmin.UserApplyCcdHandler)
}
if *backend.AuthByPassword {
http.HandleFunc("/api/user/change-password", ovpnAdmin.UserChangePasswordHandler)
http.HandleFunc("/api/auth/check", ovpnAdmin.AuthCheckHandler)
if *backend.AuthTFA {
http.HandleFunc("/api/user/2fa/secret", ovpnAdmin.UserGetSecretHandler)
http.HandleFunc("/api/user/2fa/register", ovpnAdmin.UserSetupTFAHandler)
http.HandleFunc("/api/user/2fa/reset", ovpnAdmin.UserResetTFAHandler)
if ovpnAdmin.ExtraAuth {
http.HandleFunc(listenBaseUrl + "api/user/change-password", ovpnAdmin.UserChangePasswordHandler)
http.HandleFunc(listenBaseUrl + "api/auth/check", ovpnAdmin.AuthCheckHandler)
if *backend.AuthType == "TOTP" {
http.HandleFunc(listenBaseUrl + "api/user/2fa/secret", ovpnAdmin.UserGetSecretHandler)
http.HandleFunc(listenBaseUrl + "api/user/2fa/register", ovpnAdmin.UserSetupTFAHandler)
http.HandleFunc(listenBaseUrl + "api/user/2fa/reset", ovpnAdmin.UserResetTFAHandler)
}
}
http.HandleFunc("/api/sync/last/try", ovpnAdmin.LastSyncTimeHandler)
http.HandleFunc("/api/sync/last/successful", ovpnAdmin.LastSuccessfulSyncTimeHandler)
http.HandleFunc(backend.DownloadCertsApiUrl, ovpnAdmin.DownloadCertsHandler)
http.HandleFunc(backend.DownloadCcdApiUrl, ovpnAdmin.DownloadCcdHandler)
http.HandleFunc(listenBaseUrl + "api/sync/last/try", ovpnAdmin.LastSyncTimeHandler)
http.HandleFunc(listenBaseUrl + "api/sync/last/successful", ovpnAdmin.LastSuccessfulSyncTimeHandler)
http.HandleFunc(listenBaseUrl + backend.DownloadCertsApiUrl, ovpnAdmin.DownloadCertsHandler)
http.HandleFunc(listenBaseUrl + backend.DownloadCcdApiUrl, ovpnAdmin.DownloadCcdHandler)
http.Handle(*backend.MetricsPath, promhttp.HandlerFor(ovpnAdmin.PromRegistry, promhttp.HandlerOpts{}))
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
http.HandleFunc(listenBaseUrl + "ping", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "pong")
})
log.Printf("Bind: http://%s:%s", *backend.ListenHost, *backend.ListenPort)
log.Printf("Bind: http://%s:%s%s", *backend.ListenHost, *backend.ListenPort, listenBaseUrl)
log.Fatal(http.ListenAndServe(*backend.ListenHost+":"+*backend.ListenPort, nil))
}

View File

@ -1,5 +1,5 @@
#!/usr/bin/env sh
source /etc/openvpn/scripts/.env
PATH=$PATH:/usr/local/bin
set -e
@ -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${OVPN_LISTEN_BASE_URL}api/auth/check
else
echo "$(date) Authorization for user $common_name failed"
exit 1

View File

@ -19,17 +19,8 @@ else
do
sleep 5
done
else
echo "Generating new certs"
easyrsa init-pki
cp -R /usr/share/easy-rsa/* $EASY_RSA_LOC/pki
echo "ca" | easyrsa build-ca nopass
easyrsa build-server-full server nopass
easyrsa gen-dh
openvpn --genkey --secret ./pki/ta.key
fi
fi
easyrsa gen-crl
iptables -t nat -D POSTROUTING -s ${OVPN_SRV_NET}/${OVPN_SRV_MASK} ! -d ${OVPN_SRV_NET}/${OVPN_SRV_MASK} -j MASQUERADE || true
iptables -t nat -A POSTROUTING -s ${OVPN_SRV_NET}/${OVPN_SRV_MASK} ! -d ${OVPN_SRV_NET}/${OVPN_SRV_MASK} -j MASQUERADE
@ -41,8 +32,9 @@ 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/
echo OVPN_LISTEN_BASE_URL=${OVPN_LISTEN_BASE_URL} > /etc/openvpn/scripts/.env
cp -f /etc/openvpn/setup/auth.sh /etc/openvpn/scripts/auth.sh
chmod +x /etc/openvpn/scripts/auth.sh
echo "auth-user-pass-verify /etc/openvpn/scripts/auth.sh via-file" | tee -a /etc/openvpn/openvpn.conf