Compare commits

...

2 Commits

Author SHA1 Message Date
Paramoshka
a2c41756a5
Added the ability to set the validity period of the client certificate. (#301)
Signed-off-by: Paramoshka <parfenov_ivan_42a@mail.ru>
2025-03-03 13:45:33 +01:00
Izhikov Matvey
ac96942e1d
Ovpn user call and mgmt fixes + added new flag for init users db (#296) 2025-03-03 12:19:53 +01:00
4 changed files with 59 additions and 30 deletions

View File

@ -157,7 +157,10 @@ Flags:
--auth.db="./easyrsa/pki/users.db" --auth.db="./easyrsa/pki/users.db"
(or OVPN_AUTH_DB_PATH) database path for password authorization (or OVPN_AUTH_DB_PATH) database path for password authorization
--auth.db-init
(or OVPN_AUTH_DB_INIT) enable database init if user db not exists or size is 0
--log.level set log level: trace, debug, info, warn, error (default info) --log.level set log level: trace, debug, info, warn, error (default info)
(or LOG_LEVEL) (or LOG_LEVEL)

View File

@ -8,7 +8,9 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt"
"math/big" "math/big"
"strconv"
"time" "time"
) )
@ -60,7 +62,6 @@ func genPrivKey() (privKeyPEM *bytes.Buffer, err error) {
Bytes: privKeyPKCS8, Bytes: privKeyPKCS8,
}) })
return return
} }
@ -133,6 +134,17 @@ func genClientCert(privKey, caPrivKey *rsa.PrivateKey, ca *x509.Certificate, cn
serialNumberRange := new(big.Int).Lsh(big.NewInt(1), 128) serialNumberRange := new(big.Int).Lsh(big.NewInt(1), 128)
serial, err := rand.Int(rand.Reader, serialNumberRange) serial, err := rand.Int(rand.Reader, serialNumberRange)
certLifetimeDays, err := strconv.Atoi(*clientCertExpirationDays)
if err != nil {
return nil, fmt.Errorf("can't get client certificate expiration value: %w", err)
}
notBefore := time.Now()
notAfter := notBefore.Add(time.Duration(certLifetimeDays) * 24 * time.Hour)
if notAfter.After(ca.NotAfter) {
notAfter = ca.NotAfter
}
template := x509.Certificate{ template := x509.Certificate{
BasicConstraintsValid: true, BasicConstraintsValid: true,
DNSNames: []string{cn}, DNSNames: []string{cn},
@ -142,8 +154,8 @@ func genClientCert(privKey, caPrivKey *rsa.PrivateKey, ca *x509.Certificate, cn
}, },
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
NotBefore: time.Now(), NotBefore: notBefore,
NotAfter: ca.NotAfter, NotAfter: notAfter,
} }
issuerBytes, err := x509.CreateCertificate(rand.Reader, &template, ca, &privKey.PublicKey, caPrivKey) issuerBytes, err := x509.CreateCertificate(rand.Reader, &template, ca, &privKey.PublicKey, caPrivKey)

64
main.go
View File

@ -9,11 +9,7 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"github.com/google/uuid"
"io/ioutil" "io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"net" "net"
"net/http" "net/http"
"os" "os"
@ -25,6 +21,11 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/google/uuid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"github.com/gobuffalo/packr/v2" "github.com/gobuffalo/packr/v2"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
@ -71,9 +72,11 @@ var (
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() authByPassword = kingpin.Flag("auth.password", "enable additional password authentication").Default("false").Envar("OVPN_AUTH").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()
authDataBaseInit = kingpin.Flag("auth.db-init", "enable database initialization if db user not exists or size is 0").Default("false").Envar("OVPN_AUTH_DB_INIT").Bool()
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()
logFormat = kingpin.Flag("log.format", "set log format: text, json (default text)").Default("text").Envar("LOG_FORMAT").String() logFormat = kingpin.Flag("log.format", "set log format: text, json (default text)").Default("text").Envar("LOG_FORMAT").String()
storageBackend = kingpin.Flag("storage.backend", "storage backend: filesystem, kubernetes.secrets (default filesystem)").Default("filesystem").Envar("STORAGE_BACKEND").String() storageBackend = kingpin.Flag("storage.backend", "storage backend: filesystem, kubernetes.secrets (default filesystem)").Default("filesystem").Envar("STORAGE_BACKEND").String()
clientCertExpirationDays = kingpin.Flag("client-cert.expiration-days", "Expiration period of OpenVPN client certificates in days, the period will shrink automatically to the CA expiration period").Default("3650").Envar("CLIENT_CERT_EXPIRATION_DAYS").String()
certsArchivePath = "/tmp/" + certsArchiveFileName certsArchivePath = "/tmp/" + certsArchiveFileName
ccdArchivePath = "/tmp/" + ccdArchiveFileName ccdArchivePath = "/tmp/" + ccdArchiveFileName
@ -504,6 +507,10 @@ func main() {
*indexTxtPath = *easyrsaDirPath + "/pki/index.txt" *indexTxtPath = *easyrsaDirPath + "/pki/index.txt"
} }
if *authDataBaseInit {
ovpnUserInitDb()
}
ovpnAdmin := new(OvpnAdmin) ovpnAdmin := new(OvpnAdmin)
ovpnAdmin.lastSyncTime = "unknown" ovpnAdmin.lastSyncTime = "unknown"
@ -558,27 +565,27 @@ func main() {
static := CacheControlWrapper(http.FileServer(staticBox)) static := CacheControlWrapper(http.FileServer(staticBox))
http.Handle(*listenBaseUrl, http.StripPrefix(strings.TrimRight(*listenBaseUrl, "/"), static)) http.Handle(*listenBaseUrl, http.StripPrefix(strings.TrimRight(*listenBaseUrl, "/"), static))
http.HandleFunc(*listenBaseUrl + "api/server/settings", ovpnAdmin.serverSettingsHandler) http.HandleFunc(*listenBaseUrl+"api/server/settings", ovpnAdmin.serverSettingsHandler)
http.HandleFunc(*listenBaseUrl + "api/users/list", ovpnAdmin.userListHandler) http.HandleFunc(*listenBaseUrl+"api/users/list", ovpnAdmin.userListHandler)
http.HandleFunc(*listenBaseUrl + "api/user/create", ovpnAdmin.userCreateHandler) http.HandleFunc(*listenBaseUrl+"api/user/create", ovpnAdmin.userCreateHandler)
http.HandleFunc(*listenBaseUrl + "api/user/change-password", ovpnAdmin.userChangePasswordHandler) http.HandleFunc(*listenBaseUrl+"api/user/change-password", ovpnAdmin.userChangePasswordHandler)
http.HandleFunc(*listenBaseUrl + "api/user/rotate", ovpnAdmin.userRotateHandler) http.HandleFunc(*listenBaseUrl+"api/user/rotate", ovpnAdmin.userRotateHandler)
http.HandleFunc(*listenBaseUrl + "api/user/delete", ovpnAdmin.userDeleteHandler) http.HandleFunc(*listenBaseUrl+"api/user/delete", ovpnAdmin.userDeleteHandler)
http.HandleFunc(*listenBaseUrl + "api/user/revoke", ovpnAdmin.userRevokeHandler) http.HandleFunc(*listenBaseUrl+"api/user/revoke", ovpnAdmin.userRevokeHandler)
http.HandleFunc(*listenBaseUrl + "api/user/unrevoke", ovpnAdmin.userUnrevokeHandler) http.HandleFunc(*listenBaseUrl+"api/user/unrevoke", ovpnAdmin.userUnrevokeHandler)
http.HandleFunc(*listenBaseUrl + "api/user/config/show", ovpnAdmin.userShowConfigHandler) http.HandleFunc(*listenBaseUrl+"api/user/config/show", ovpnAdmin.userShowConfigHandler)
http.HandleFunc(*listenBaseUrl + "api/user/disconnect", ovpnAdmin.userDisconnectHandler) http.HandleFunc(*listenBaseUrl+"api/user/disconnect", ovpnAdmin.userDisconnectHandler)
http.HandleFunc(*listenBaseUrl + "api/user/statistic", ovpnAdmin.userStatisticHandler) http.HandleFunc(*listenBaseUrl+"api/user/statistic", ovpnAdmin.userStatisticHandler)
http.HandleFunc(*listenBaseUrl + "api/user/ccd", ovpnAdmin.userShowCcdHandler) http.HandleFunc(*listenBaseUrl+"api/user/ccd", ovpnAdmin.userShowCcdHandler)
http.HandleFunc(*listenBaseUrl + "api/user/ccd/apply", ovpnAdmin.userApplyCcdHandler) http.HandleFunc(*listenBaseUrl+"api/user/ccd/apply", ovpnAdmin.userApplyCcdHandler)
http.HandleFunc(*listenBaseUrl + "api/sync/last/try", ovpnAdmin.lastSyncTimeHandler) http.HandleFunc(*listenBaseUrl+"api/sync/last/try", ovpnAdmin.lastSyncTimeHandler)
http.HandleFunc(*listenBaseUrl + "api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler) http.HandleFunc(*listenBaseUrl+"api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler)
http.HandleFunc(*listenBaseUrl + downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler) http.HandleFunc(*listenBaseUrl+downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler)
http.HandleFunc(*listenBaseUrl + downloadCcdApiUrl, ovpnAdmin.downloadCcdHandler) http.HandleFunc(*listenBaseUrl+downloadCcdApiUrl, ovpnAdmin.downloadCcdHandler)
http.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{})) http.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{}))
http.HandleFunc(*listenBaseUrl + "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")
}) })
@ -1053,7 +1060,7 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
} }
if *authByPassword { if *authByPassword {
o := runBash(fmt.Sprintf("openvpn-user revoke --db-path %s --user %s", *authDatabase, username)) o := runBash(fmt.Sprintf("openvpn-user revoke --db.path %s --user %s", *authDatabase, username))
log.Debug(o) log.Debug(o)
} }
@ -1115,7 +1122,7 @@ func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) {
_ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath)) _ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath))
if *authByPassword { if *authByPassword {
o := runBash(fmt.Sprintf("openvpn-user restore --db-path %s --user %s", *authDatabase, username)) o := runBash(fmt.Sprintf("openvpn-user restore --db.path %s --user %s", *authDatabase, username))
log.Debug(o) log.Debug(o)
} }
@ -1340,7 +1347,7 @@ func (oAdmin *OvpnAdmin) mgmtGetActiveClients() []clientStatus {
break break
} }
oAdmin.mgmtRead(conn) // read welcome message oAdmin.mgmtRead(conn) // read welcome message
conn.Write([]byte("status\n")) conn.Write([]byte("status 1\n"))
activeClients = append(activeClients, oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn), srv)...) activeClients = append(activeClients, oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn), srv)...)
conn.Close() conn.Close()
} }
@ -1501,6 +1508,13 @@ func unArchiveCcd() {
} }
} }
func ovpnUserInitDb() {
if fi, err := os.Stat(*authDatabase); errors.Is(err, os.ErrNotExist) || fi.Size() == 0 {
i := runBash(fmt.Sprintf("openvpn-user --db.path %[1]s db-init && openvpn-user --db.path %[1]s db-migrate", *authDatabase))
log.Debug(i)
}
}
func (oAdmin *OvpnAdmin) syncDataFromMaster() { func (oAdmin *OvpnAdmin) syncDataFromMaster() {
retryCountMax := 3 retryCountMax := 3
certsDownloadFailed := true certsDownloadFailed := true

View File

@ -48,7 +48,7 @@ if [ ${OVPN_PASSWD_AUTH} = "true" ]; then
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
echo "script-security 2" | tee -a /etc/openvpn/openvpn.conf echo "script-security 2" | tee -a /etc/openvpn/openvpn.conf
echo "verify-client-cert require" | tee -a /etc/openvpn/openvpn.conf echo "verify-client-cert require" | tee -a /etc/openvpn/openvpn.conf
openvpn-user db-init --db.path=$EASY_RSA_LOC/pki/users.db openvpn-user db-init --db.path=$EASY_RSA_LOC/pki/users.db && openvpn-user db-migrate --db.path=$EASY_RSA_LOC/pki/users.db
fi fi
[ -d $EASY_RSA_LOC/pki ] && chmod 755 $EASY_RSA_LOC/pki [ -d $EASY_RSA_LOC/pki ] && chmod 755 $EASY_RSA_LOC/pki