1
0
Fork 0
mirror of synced 2024-05-26 20:11:14 -04:00
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_NET="192.168.100.0"
OVPN_SERVER_MASK="255.255.255.0" OVPN_SERVER_MASK="255.255.255.0"
OVPN_NETWORK="192.168.100.0/24" OVPN_NETWORK="192.168.100.0/24"
@ -6,8 +7,6 @@ OVPN_CCD_PATH="/mnt/ccd"
EASYRSA_PATH="/mnt/easyrsa" EASYRSA_PATH="/mnt/easyrsa"
OVPN_INDEX_PATH="/mnt/easyrsa/pki/index.txt" OVPN_INDEX_PATH="/mnt/easyrsa/pki/index.txt"
OVPN_SERVER="127.0.0.1:7777:tcp" OVPN_SERVER="127.0.0.1:7777:tcp"
OVPN_AUTH="true" OVPN_AUTH="TOTP"
OVPN_AUTH_TFA="true"
OVPN_PASSWD_AUTH="true"
OVPN_AUTH_DB_PATH="/mnt/easyrsa/pki/users.db" OVPN_AUTH_DB_PATH="/mnt/easyrsa/pki/users.db"
LOG_LEVEL="debug" LOG_LEVEL="debug"

View file

@ -12,11 +12,24 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Push ovpn-admin image to Docker Hub - name: Set up Docker Buildx
uses: docker/build-push-action@v1 uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }} password: ${{ secrets.DOCKER_PASS }}
repository: flant/ovpn-admin - name: Push openvpn image to Docker Hub
tags: latest uses: docker/build-push-action@v4
dockerfile: Dockerfile 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 - name: Get the version
id: get_version id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: Push ovpn-admin image to Docker Hub - name: Set up Docker Buildx
uses: docker/build-push-action@v1 uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }} password: ${{ secrets.DOCKER_PASS }}
repository: flant/ovpn-admin - name: Push openvpn image to Docker Hub
tags: ${{ steps.get_version.outputs.VERSION }} uses: docker/build-push-action@v4
dockerfile: Dockerfile 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 - name: checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: build binaries - name: build binaries
uses: wangyoucao577/go-release-action@v1.28 uses: wangyoucao577/go-release-action@v1.40
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
goversion: 1.17 goversion: 1.17

View file

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

View file

@ -1,18 +1,21 @@
FROM node:16-alpine3.15 AS frontend-builder FROM node:16-alpine3.15 AS frontend-builder
COPY frontend/ /app 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 FROM golang:1.17.3-buster AS backend-builder
COPY --from=frontend-builder /app/static /app/frontend/static COPY --from=frontend-builder /app/static /app/frontend/static
COPY . /app 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 FROM alpine:3.16
WORKDIR /app WORKDIR /app
ARG TARGETARCH
RUN apk add --update bash easy-rsa openssl openvpn coreutils iptables curl&& \ RUN apk add --update bash easy-rsa openssl openvpn coreutils iptables curl&& \
ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \ 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/* 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 --from=backend-builder /app/ovpn-admin /app
COPY setup/ /etc/openvpn/setup COPY setup/ /etc/openvpn/setup
RUN chmod +x /etc/openvpn/setup/configure.sh RUN chmod +x /etc/openvpn/setup/configure.sh

View file

@ -97,6 +97,9 @@ Flags:
--listen.port="8080" port for ovpn-admin --listen.port="8080" port for ovpn-admin
(or OVPN_LISTEN_PORT) (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 --role="master" server role, master or slave
(or OVPN_ROLE) (or OVPN_ROLE)

View file

@ -11,4 +11,16 @@ const (
stringDateFormat = "2006-01-02 15:04:05" stringDateFormat = "2006-01-02 15:04:05"
KubeNamespaceFilePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" 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") userIsNotActiveError = errors.New("user is not active")
passwordMismatchedError = errors.New("password mismatched") passwordMismatchedError = errors.New("password mismatched")
tokenMismatchedError = errors.New("token mismatched") tokenMismatchedError = errors.New("token mismatched")
checkAppError = errors.New("failed to check 2FA app") checkAppError = errors.New("failed to check 2FA TOTP app")
registerAppError = errors.New("failed to register 2FA app") registerAppError = errors.New("failed to register 2FA TOTP app")
authBackendDisabled = errors.New("auth backend not enabled yet") authBackendDisabled = errors.New("auth backend not enabled yet")
) )

View file

@ -3,9 +3,10 @@ package backend
import "gopkg.in/alecthomas/kingpin.v2" import "gopkg.in/alecthomas/kingpin.v2"
var ( var (
ListenHost = kingpin.Flag("listen.host", "host for ovpn-admin").Default("0.0.0.0").Envar("OVPN_LISTEN_HOST").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() 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() 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() //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() //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() 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() 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() 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() 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() 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() 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() AuthType = kingpin.Flag("auth.type", "auth type").Default("").Envar("OVPN_AUTH").HintOptions("TOTP", "PASSWORD", "").String()
AuthTFA = kingpin.Flag("auth.2fa", "auth type").Default("false").Envar("OVPN_AUTH_TFA").Bool()
AuthDatabase = kingpin.Flag("auth.db", "database path for password authentication").Default("./easyrsa/pki/users.db").Envar("OVPN_AUTH_DB_PATH").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() 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() oAdmin.clients = oAdmin.usersList()
} }
usersList, _ := json.Marshal(oAdmin.clients) usersList, _ := json.Marshal(oAdmin.clients)
fmt.Fprintf(w, "%s", usersList) 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) http.Error(w, err.Error(), http.StatusBadRequest)
} else { } else {
w.WriteHeader(http.StatusOK) 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) { func (oAdmin *OvpnAdmin) UserChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
log.Info(r.RemoteAddr, " ", r.RequestURI) log.Info(r.RemoteAddr, " ", r.RequestURI)
_ = r.ParseForm() _ = r.ParseForm()
if *AuthByPassword { if oAdmin.ExtraAuth {
err, msg := oAdmin.userChangePassword(r.FormValue("username"), r.FormValue("password")) err, msg := oAdmin.userChangePassword(r.FormValue("username"), r.FormValue("password"))
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)

View file

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

View file

@ -14,7 +14,6 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/google/uuid"
ou "github.com/pashcovich/openvpn-user/src" ou "github.com/pashcovich/openvpn-user/src"
log "github.com/sirupsen/logrus" 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.Key = fRead(*EasyrsaDirPath + "/pki/private/" + username + ".key")
} }
conf.PasswdAuth = *AuthByPassword conf.PasswdAuth = oAdmin.ExtraAuth
t := oAdmin.getTemplate("client.conf.tpl", "client-config", *clientConfigTemplatePath) t := oAdmin.getTemplate("client.conf.tpl", "client-config", *clientConfigTemplatePath)
@ -280,7 +279,7 @@ func (oAdmin *OvpnAdmin) usersList() []OpenvpnClient {
apochNow := time.Now().Unix() apochNow := time.Now().Unix()
for _, line := range IndexTxtParser(fRead(*IndexTxtPath)) { 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 totalCerts += 1
ovpnClient := OpenvpnClient{Identity: line.Identity, ExpirationDate: parseDateToString(indexTxtDateLayout, line.ExpirationDate, stringDateFormat)} ovpnClient := OpenvpnClient{Identity: line.Identity, ExpirationDate: parseDateToString(indexTxtDateLayout, line.ExpirationDate, stringDateFormat)}
switch { switch {
@ -313,8 +312,12 @@ func (oAdmin *OvpnAdmin) usersList() []OpenvpnClient {
connectedUniqUsers += 1 connectedUniqUsers += 1
} }
if oAdmin.isSecondFactorConfigured(ovpnClient.Identity) { if oAdmin.ExtraAuth{
ovpnClient.SecondFactor = true if oAdmin.isSecondFactorConfigured(ovpnClient.Identity) {
ovpnClient.SecondFactor = "enabled"
} else {
ovpnClient.SecondFactor = "disabled"
}
} }
users = append(users, ovpnClient) users = append(users, ovpnClient)
@ -354,40 +357,35 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (string, error) {
return err.Error(), err return err.Error(), err
} }
if *AuthByPassword { if oAdmin.ExtraAuth {
if err := validatePassword(password); err != nil { if err := validatePassword(password); err != nil {
log.Debugf("userCreate: authByPassword(): %s", err.Error()) log.Debugf("userCreate: authByPassword(): %s", err.Error())
return err.Error(), err return err.Error(), err
} }
} }
if *StorageBackend == "kubernetes.secrets" { err := oAdmin.PKI.BuildKeyPairClient(username)
err := oAdmin.KubeClient.EasyrsaBuildClient(username) if err != nil {
if err != nil { log.Error(err)
log.Error(err) return err.Error(), err
return err.Error(), err }
}
if *AuthByPassword { if oAdmin.ExtraAuth {
switch *StorageBackend {
case "kubernetes.secrets":
err = oAdmin.KubeClient.updatePasswordSecret(username, []byte(password)) err = oAdmin.KubeClient.updatePasswordSecret(username, []byte(password))
if err != nil { if err != nil {
return err.Error(), err return err.Error(), err
} }
} case "filesystems":
} else {
o := runBash(fmt.Sprintf("cd %s && easyrsa build-client-full %s nopass 1>/dev/null", *EasyrsaDirPath, username))
log.Debug(o)
if *AuthByPassword {
_, err := oAdmin.OUser.CreateUser(username, password) _, err := oAdmin.OUser.CreateUser(username, password)
if err != nil { if err != nil {
return err.Error(), err return err.Error(), err
} }
} }
} }
log.Infof("Certificate for user %s issued", username) log.Infof("Certificate for user %s issued", username)
//oAdmin.clients = oAdmin.usersList()
return "", nil return "", nil
} }
@ -405,13 +403,13 @@ func (oAdmin *OvpnAdmin) userChangePassword(username, password string) (error, s
log.Warningf("userChangePassword: %s", err.Error()) log.Warningf("userChangePassword: %s", err.Error())
return err, err.Error() return err, err.Error()
} }
switch *StorageBackend {
if *StorageBackend == "kubernetes.secrets" { case "kubernetes.secrets":
err := oAdmin.KubeClient.updatePasswordSecret(username, []byte(password)) err := oAdmin.KubeClient.updatePasswordSecret(username, []byte(password))
if err != nil { if err != nil {
return err, err.Error() return err, err.Error()
} }
} else { case "filesystem":
msg, err := oAdmin.OUser.ChangeUserPassword(username, password) msg, err := oAdmin.OUser.ChangeUserPassword(username, password)
if err != nil { if err != nil {
return err, msg return err, msg
@ -435,14 +433,21 @@ func (oAdmin *OvpnAdmin) isSecondFactorConfigured(username string) bool {
} }
return sfe return sfe
case "filesystem": case "filesystem":
sfe, err := oAdmin.OUser.IsSecondFactorEnabled(username) switch *AuthType {
if err != nil { case "TOTP":
return false 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: default:
return false return false
} }
return false
} }
func (oAdmin *OvpnAdmin) getUserSecret(username string) (string, error) { 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 { for i, u := range oAdmin.clients {
if u.Identity == username { 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 nil
} }
return fmt.Errorf("user \"%s\" not found", username) 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 { for i, u := range oAdmin.clients {
if u.Identity == username { if u.Identity == username {
oAdmin.clients[i].SecondFactor = false oAdmin.clients[i].SecondFactor = "disabled"
} }
} }
return nil return nil
@ -587,7 +592,12 @@ func (oAdmin *OvpnAdmin) checkAuth(username, token string) error {
return authErr return authErr
} }
} else { } 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 { if authErr != nil {
return authErr return authErr
} }
@ -614,19 +624,13 @@ func (oAdmin *OvpnAdmin) getUserStatistic(username string) []ClientStatus {
func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) { func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
log.Infof("Revoke certificate for user %s", username) log.Infof("Revoke certificate for user %s", username)
if checkUserExist(username) { if checkUserExist(username) {
// check certificate valid flag 'V' err := oAdmin.PKI.CertificateRevoke(username)
if *StorageBackend == "kubernetes.secrets" { if err != nil {
err := oAdmin.KubeClient.EasyrsaRevoke(username) log.Error(err)
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)
} }
if *AuthByPassword { if *StorageBackend == "filesystem" {
if oAdmin.OUser.CheckUserExistent(username) { if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) {
revokeMsg, revokeErr := oAdmin.OUser.RevokedUser(username) revokeMsg, revokeErr := oAdmin.OUser.RevokedUser(username)
log.Debug(revokeMsg) log.Debug(revokeMsg)
log.Debug(revokeErr) log.Debug(revokeErr)
@ -636,7 +640,6 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
} }
} }
crlFix()
userConnected, userConnectedTo := isUserConnected(username, oAdmin.activeClients) userConnected, userConnectedTo := isUserConnected(username, oAdmin.activeClients)
log.Tracef("User %s connected: %t", username, userConnected) log.Tracef("User %s connected: %t", username, userConnected)
if userConnected { if userConnected {
@ -655,67 +658,23 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) { func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) {
if checkUserExist(username) { 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" err := oAdmin.PKI.CertificateUnRevoke(username)
usersFromIndexTxt[i].RevocationDate = "" 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 *StorageBackend == "filesystem" {
if err != nil { if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) {
log.Error(err) restoreMsg, restoreErr := oAdmin.OUser.RestoreUser(username)
} log.Debug(restoreMsg)
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)) log.Debug(restoreErr)
if err != nil { if restoreErr != nil {
log.Error(err) return restoreErr, ""
}
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
}
} }
} }
err := fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt))
if err != nil {
log.Error(err)
}
} }
crlFix()
oAdmin.clients = oAdmin.usersList() oAdmin.clients = oAdmin.usersList()
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully unrevoked\"}", username) 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) { func (oAdmin *OvpnAdmin) userRotate(username, newPassword string) (error, string) {
if checkUserExist(username) { if checkUserExist(username) {
if *StorageBackend == "kubernetes.secrets" { err := oAdmin.PKI.CertificateRotate(username)
err := oAdmin.KubeClient.EasyrsaRotate(username) if err != nil {
if err != nil { log.Error(err)
log.Error(err) }
}
} else { if *StorageBackend == "filesystem" {
if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) {
var oldUserIndex, newUserIndex int deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true)
var oldUserSerial string log.Debug(deleteMsg)
log.Debug(deleteErr)
uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) if deleteErr != nil {
return deleteErr, ""
usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath)) }
for i := range usersFromIndexTxt { log.Debug(deleteMsg)
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))
} }
crlFix()
oAdmin.clients = oAdmin.usersList() oAdmin.clients = oAdmin.usersList()
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully rotated\"}", username) 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) return fmt.Errorf("user \"%s\" not found", username), fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username)
} }
func (oAdmin *OvpnAdmin) userDelete(username string) (error, string) { func (oAdmin *OvpnAdmin) userDelete(username string) (error, string) {
if checkUserExist(username) { if checkUserExist(username) {
if *StorageBackend == "kubernetes.secrets" { err := oAdmin.PKI.CertificateDelAfterRevoke(username)
err := oAdmin.KubeClient.EasyrsaDelete(username) if err != nil {
if err != nil { log.Error(err)
log.Error(err) }
}
} else { if *StorageBackend == "filesystem" {
uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) {
usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath)) deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true)
for i := range usersFromIndexTxt { log.Debug(deleteMsg)
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username { log.Debug(deleteErr)
usersFromIndexTxt[i].DistinguishedName = "/CN=REVOKED-" + username + "-" + uniqHash if deleteErr != nil {
break return deleteErr, ""
} }
} log.Debug(deleteMsg)
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))
} }
crlFix()
oAdmin.clients = oAdmin.usersList() oAdmin.clients = oAdmin.usersList()
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully deleted\"}", username) return nil, fmt.Sprintf("{\"msg\":\"User %s successfully deleted\"}", username)
} }
@ -1095,3 +981,17 @@ func (oAdmin *OvpnAdmin) SyncWithMaster() {
oAdmin.SyncDataFromMaster() 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 ( import (
"io/fs" "io/fs"
"sync" "sync"
"bytes"
"time"
"crypto/rsa"
"crypto/x509"
"k8s.io/client-go/kubernetes"
"github.com/pashcovich/openvpn-user/src" "github.com/pashcovich/openvpn-user/src"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
@ -19,11 +24,13 @@ type OvpnAdmin struct {
PromRegistry *prometheus.Registry PromRegistry *prometheus.Registry
OUser *src.OpenvpnUser OUser *src.OpenvpnUser
KubeClient *OpenVPNPKI KubeClient *OpenVPNPKI
PKI *OpenVPNPKI
MgmtInterfaces map[string]string MgmtInterfaces map[string]string
Templates fs.FS Templates fs.FS
Modules []string Modules []string
mgmtStatusTimeFormat string mgmtStatusTimeFormat string
CreateUserMutex *sync.Mutex CreateUserMutex *sync.Mutex
ExtraAuth bool
} }
type OpenvpnServer struct { type OpenvpnServer struct {
@ -48,7 +55,7 @@ type OpenvpnClient struct {
RevocationDate string `json:"RevocationDate"` RevocationDate string `json:"RevocationDate"`
ConnectionStatus string `json:"ConnectionStatus"` ConnectionStatus string `json:"ConnectionStatus"`
Connections int `json:"Connections"` Connections int `json:"Connections"`
SecondFactor bool `json:"SecondFactor"` SecondFactor string `json:"SecondFactor,omitempty"`
} }
type ccdRoute struct { type ccdRoute struct {
@ -85,3 +92,32 @@ type ClientStatus struct {
LastRefFormatted string LastRefFormatted string
ConnectedTo 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" "compress/gzip"
"context" "context"
"crypto/x509" "crypto/x509"
"database/sql"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil" "io/fs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@ -208,7 +209,7 @@ func getOvpnServerHostsFromKubeApi() ([]OpenvpnServer, error) {
func getOvpnCaCertExpireDate() time.Time { func getOvpnCaCertExpireDate() time.Time {
caCertPath := *EasyrsaDirPath + "/pki/ca.crt" caCertPath := *EasyrsaDirPath + "/pki/ca.crt"
caCert, err := ioutil.ReadFile(caCertPath) caCert, err := os.ReadFile(caCertPath)
if err != nil { if err != nil {
log.Errorf("error read file %s: %s", caCertPath, err.Error()) log.Errorf("error read file %s: %s", caCertPath, err.Error())
} }
@ -277,13 +278,27 @@ func fExist(path string) bool {
} }
func fRead(path string) string { 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 { if err != nil {
log.Warning(err) 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 { func fCreate(path string) error {
@ -300,7 +315,15 @@ func fCreate(path string) error {
} }
func fWrite(path, content 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -394,7 +417,7 @@ func fDownload(path, url string, basicAuth bool) error {
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return err
} }
@ -555,3 +578,34 @@ func randStr(strSize int, randType string) string {
} }
return string(bytes) 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": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -90,7 +90,7 @@
{ {
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -149,7 +149,7 @@
{ {
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -206,7 +206,7 @@
{ {
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -263,7 +263,7 @@
{ {
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -305,7 +305,7 @@
}, },
"textMode": "auto" "textMode": "auto"
}, },
"pluginVersion": "8.5.2", "pluginVersion": "8.5.13",
"targets": [ "targets": [
{ {
"expr": "ovpn_clients_expired", "expr": "ovpn_clients_expired",
@ -320,7 +320,7 @@
{ {
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -381,7 +381,7 @@
"dashes": false, "dashes": false,
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -469,7 +469,7 @@
"dashes": false, "dashes": false,
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -557,7 +557,7 @@
"dashes": false, "dashes": false,
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -647,7 +647,7 @@
"dashes": false, "dashes": false,
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -733,7 +733,7 @@
{ {
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"description": "value show last connection check time", "description": "value show last connection check time",
"fieldConfig": { "fieldConfig": {
@ -794,7 +794,7 @@
{ {
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"description": "value shows when connection was started", "description": "value shows when connection was started",
"fieldConfig": { "fieldConfig": {
@ -855,7 +855,7 @@
{ {
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "P0D6E4079E36703EB" "uid": "$ds_prometheus"
}, },
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": {
@ -928,7 +928,26 @@
"style": "dark", "style": "dark",
"tags": [], "tags": [],
"templating": { "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": { "time": {
"from": "now-15m", "from": "now-15m",
@ -952,4 +971,4 @@
"uid": "Z7qmFI0Gk", "uid": "Z7qmFI0Gk",
"version": 1, "version": 1,
"weekStart": "" "weekStart": ""
} }

View file

@ -9,7 +9,8 @@ services:
environment: environment:
OVPN_SERVER_NET: ${OVPN_SERVER_NET} OVPN_SERVER_NET: ${OVPN_SERVER_NET}
OVPN_SERVER_MASK: ${OVPN_SERVER_MASK} 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: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
@ -26,12 +27,12 @@ services:
environment: environment:
OVPN_NETWORK: ${OVPN_NETWORK} OVPN_NETWORK: ${OVPN_NETWORK}
OVPN_CCD: ${OVPN_CCD} OVPN_CCD: ${OVPN_CCD}
OVPN_CCD_PATH: ${OVPN_CCD_PATH OVPN_CCD_PATH: ${OVPN_CCD_PATH}
EASYRSA_PATH: ${EASYRSA_PATH} EASYRSA_PATH: ${EASYRSA_PATH}
OVPN_SERVER: ${OVPN_SERVER} OVPN_SERVER: ${OVPN_SERVER}
OVPN_INDEX_PATH: ${OVPN_INDEX_PATH} OVPN_INDEX_PATH: ${OVPN_INDEX_PATH}
OVPN_LISTEN_BASE_URL: ${OVPN_LISTEN_BASE_URL}
OVPN_AUTH: ${OVPN_AUTH} OVPN_AUTH: ${OVPN_AUTH}
OVPN_AUTH_TFA: ${OVPN_AUTH_TFA}
OVPN_AUTH_DB_PATH: ${OVPN_AUTH_DB_PATH} OVPN_AUTH_DB_PATH: ${OVPN_AUTH_DB_PATH}
LOG_LEVEL: ${LOG_LEVEL} LOG_LEVEL: ${LOG_LEVEL}
network_mode: service:openvpn 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", "terser-webpack-plugin": "^5.3.0",
"vue-loader": "^17.0.0", "vue-loader": "^17.0.0",
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "^2.6.14",
"webpack": "^5.65.0", "webpack": "^5.76.0",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2" "webpack-dev-server": "^4.7.2"
} }

View file

@ -160,6 +160,7 @@ new Vue({
label: 'Download config', label: 'Download config',
class: 'btn-info', class: 'btn-info',
showWhenStatus: 'Active', showWhenStatus: 'Active',
Require2FA: "true",
showForServerRole: ['master', 'slave'], showForServerRole: ['master', 'slave'],
showForModule: ["core"], showForModule: ["core"],
}, },
@ -353,7 +354,7 @@ new Vue({
getUserTFAData: function(data) { getUserTFAData: function(data) {
let _this = this; let _this = this;
if (!_this.secondfactor) { if (_this.secondfactor == 'disabled' ) {
axios.request(axios_cfg('api/user/2fa/secret', data, 'form')) axios.request(axios_cfg('api/user/2fa/secret', data, 'form'))
.then(function (response) { .then(function (response) {
_this.u.secret = response.data; _this.u.secret = response.data;
@ -374,15 +375,15 @@ new Vue({
_this.u.modalActionStatus = 200; _this.u.modalActionStatus = 200;
_this.u.modalRegister2faVisible = false; _this.u.modalRegister2faVisible = false;
_this.getUserData(); _this.getUserData();
_this.secondfactor = true; _this.secondfactor = "enabled";
_this.u.token = ""; _this.u.token = "";
_this.u.secret = ""; _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) { .catch(function(error) {
_this.u.modalActionStatus = error.response.status; _this.u.modalActionStatus = error.response.status;
_this.u.modalActionMessage = error.response.data.message; _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')) axios.request(axios_cfg('api/user/2fa/reset', data, 'form'))
.then(function(response) { .then(function(response) {
_this.u.modalActionStatus = 200; _this.u.modalActionStatus = 200;
_this.secondfactor = false; _this.secondfactor = "disabled";
_this.getUserTFAData(data); _this.getUserTFAData(data);
_this.getUserData(); _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) { .catch(function(error) {
_this.u.modalActionStatus = error.response.status; _this.u.modalActionStatus = error.response.status;
_this.u.modalActionMessage = error.response.data.message; _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" @click.left.stop="rowActionFn"
v-for="action in actions" v-for="action in actions"
v-bind:class="action.class" 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 }} {{ action.label }}
</button> </button>
</span> </span>
@ -51,7 +54,7 @@
</div> </div>
<div class="modal-body"> <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="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>
<div class="modal-footer justify-content-center" v-if="u.modalActionMessage.length > 0"> <div class="modal-footer justify-content-center" v-if="u.modalActionMessage.length > 0">
@ -232,10 +235,10 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header justify-content-center"> <div class="modal-header justify-content-center">
<h4>2FA</h4> <h4>2FA TOTP</h4>
</div> </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"> <div style="text-align: center">
<vue-qr <vue-qr
:text=u.twofaurl :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"> <input type="text" class="form-control el-square modal-el-margin" minlength="6" autocomplete="off" placeholder="Pin Code" v-model="u.token">
</div> </div>
<div class="modal-body justify-content-center" v-if="secondfactor"> <div class="modal-body justify-content-center" v-if="secondfactor == 'enabled' ">
<h4>2FA already configured for user: <strong>{{ username }}</strong></h4> <h4>2FA with TOTP already configured for user: <strong>{{ username }}</strong></h4>
</div> </div>
<div class="modal-footer justify-content-center" v-if="u.modalActionMessage.length > 0"> <div class="modal-footer justify-content-center" v-if="u.modalActionMessage.length > 0">
@ -273,8 +276,8 @@
</div> </div>
<div class="modal-footer"> <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-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" v-on:click.stop="resetUser2faApp(username)">Reset 2FA</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> <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> </div>

10
go.mod
View file

@ -35,17 +35,17 @@ require (
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // 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/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.2.0 // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.2.0 // indirect golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.4.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // 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/klog/v2 v2.40.1 // indirect
k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26 // indirect k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26 // indirect
k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 // 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-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-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.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.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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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-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.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.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-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-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.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.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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/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.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.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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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.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-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-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-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-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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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 package main
import ( import (
"database/sql"
"embed" "embed"
"fmt" "fmt"
"io/fs" "io/fs"
@ -51,9 +50,16 @@ func main() {
ovpnAdmin := new(backend.OvpnAdmin) ovpnAdmin := new(backend.OvpnAdmin)
ovpnAdmin.OUser = new(ou.OpenvpnUser) 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" { if *backend.StorageBackend == "kubernetes.secrets" {
// TODO: Check
ovpnAdmin.KubeClient = new(backend.OpenVPNPKI) ovpnAdmin.KubeClient = new(backend.OpenVPNPKI)
err := ovpnAdmin.KubeClient.Run() err := ovpnAdmin.KubeClient.KubeRun()
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
@ -85,24 +91,17 @@ func main() {
ovpnAdmin.Modules = append(ovpnAdmin.Modules, "core") ovpnAdmin.Modules = append(ovpnAdmin.Modules, "core")
if *backend.AuthByPassword { switch *backend.AuthType {
db, err := sql.Open("sqlite3", *backend.AuthDatabase) case "TOTP":
if err != nil { ovpnAdmin.ExtraAuth = true
kingpin.Fatalf(err.Error()) ovpnAdmin.OUser.Database = backend.OpenDB(*backend.AuthDatabase)
} defer ovpnAdmin.OUser.Database.Close()
defer func(db *sql.DB) { ovpnAdmin.Modules = append(ovpnAdmin.Modules, "totpAuth")
err = db.Close() case "PASSWORD":
if err != nil { ovpnAdmin.ExtraAuth = true
kingpin.Fatalf(err.Error()) ovpnAdmin.OUser.Database = backend.OpenDB(*backend.AuthDatabase)
} defer ovpnAdmin.OUser.Database.Close()
}(db)
ovpnAdmin.OUser.Database = db
ovpnAdmin.Modules = append(ovpnAdmin.Modules, "passwdAuth") ovpnAdmin.Modules = append(ovpnAdmin.Modules, "passwdAuth")
if *backend.AuthTFA {
ovpnAdmin.Modules = append(ovpnAdmin.Modules, "totpAuth")
}
} }
if *backend.CcdEnabled { if *backend.CcdEnabled {
@ -134,46 +133,47 @@ func main() {
go ovpnAdmin.UpdateState() go ovpnAdmin.UpdateState()
http.Handle("/", static) listenBaseUrl := *backend.ListenBaseUrl
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)
http.HandleFunc("/api/user/disconnect", ovpnAdmin.UserDisconnectHandler) http.Handle(listenBaseUrl, http.StripPrefix(strings.TrimRight(listenBaseUrl, "/"), static))
http.HandleFunc("/api/user/statistic", ovpnAdmin.UserStatisticHandler) 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 { if *backend.CcdEnabled {
http.HandleFunc("/api/user/ccd", ovpnAdmin.UserShowCcdHandler) http.HandleFunc(listenBaseUrl + "api/user/ccd", ovpnAdmin.UserShowCcdHandler)
http.HandleFunc("/api/user/ccd/apply", ovpnAdmin.UserApplyCcdHandler) http.HandleFunc(listenBaseUrl + "api/user/ccd/apply", ovpnAdmin.UserApplyCcdHandler)
} }
if *backend.AuthByPassword { if ovpnAdmin.ExtraAuth {
http.HandleFunc("/api/user/change-password", ovpnAdmin.UserChangePasswordHandler) http.HandleFunc(listenBaseUrl + "api/user/change-password", ovpnAdmin.UserChangePasswordHandler)
http.HandleFunc("/api/auth/check", ovpnAdmin.AuthCheckHandler) http.HandleFunc(listenBaseUrl + "api/auth/check", ovpnAdmin.AuthCheckHandler)
if *backend.AuthType == "TOTP" {
if *backend.AuthTFA { http.HandleFunc(listenBaseUrl + "api/user/2fa/secret", ovpnAdmin.UserGetSecretHandler)
http.HandleFunc("/api/user/2fa/secret", ovpnAdmin.UserGetSecretHandler) http.HandleFunc(listenBaseUrl + "api/user/2fa/register", ovpnAdmin.UserSetupTFAHandler)
http.HandleFunc("/api/user/2fa/register", ovpnAdmin.UserSetupTFAHandler) http.HandleFunc(listenBaseUrl + "api/user/2fa/reset", ovpnAdmin.UserResetTFAHandler)
http.HandleFunc("/api/user/2fa/reset", ovpnAdmin.UserResetTFAHandler)
} }
} }
http.HandleFunc("/api/sync/last/try", ovpnAdmin.LastSyncTimeHandler) http.HandleFunc(listenBaseUrl + "api/sync/last/try", ovpnAdmin.LastSyncTimeHandler)
http.HandleFunc("/api/sync/last/successful", ovpnAdmin.LastSuccessfulSyncTimeHandler) http.HandleFunc(listenBaseUrl + "api/sync/last/successful", ovpnAdmin.LastSuccessfulSyncTimeHandler)
http.HandleFunc(backend.DownloadCertsApiUrl, ovpnAdmin.DownloadCertsHandler) http.HandleFunc(listenBaseUrl + backend.DownloadCertsApiUrl, ovpnAdmin.DownloadCertsHandler)
http.HandleFunc(backend.DownloadCcdApiUrl, ovpnAdmin.DownloadCcdHandler) http.HandleFunc(listenBaseUrl + backend.DownloadCcdApiUrl, ovpnAdmin.DownloadCcdHandler)
http.Handle(*backend.MetricsPath, promhttp.HandlerFor(ovpnAdmin.PromRegistry, promhttp.HandlerOpts{})) 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") 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)) log.Fatal(http.ListenAndServe(*backend.ListenHost+":"+*backend.ListenPort, nil))
} }

View file

@ -1,5 +1,5 @@
#!/usr/bin/env sh #!/usr/bin/env sh
source /etc/openvpn/scripts/.env
PATH=$PATH:/usr/local/bin PATH=$PATH:/usr/local/bin
set -e set -e
@ -7,7 +7,7 @@ auth_usr=$(head -1 $1)
auth_secret=$(tail -1 $1) auth_secret=$(tail -1 $1)
if [ $common_name = $auth_usr ]; then 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 else
echo "$(date) Authorization for user $common_name failed" echo "$(date) Authorization for user $common_name failed"
exit 1 exit 1

View file

@ -19,17 +19,8 @@ else
do do
sleep 5 sleep 5
done 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
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 -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 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 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/ 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 cp -f /etc/openvpn/setup/auth.sh /etc/openvpn/scripts/auth.sh
chmod +x /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 echo "auth-user-pass-verify /etc/openvpn/scripts/auth.sh via-file" | tee -a /etc/openvpn/openvpn.conf