c83c581e21
* fix reuse argument multiple times * replace whitespace with tab
1622 lines
51 KiB
Go
1622 lines
51 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
"io/ioutil"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"github.com/gobuffalo/packr/v2"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"gopkg.in/alecthomas/kingpin.v2"
|
|
)
|
|
|
|
const (
|
|
usernameRegexp = `^([a-zA-Z0-9_.-@])+$`
|
|
passwordMinLength = 6
|
|
certsArchiveFileName = "certs.tar.gz"
|
|
ccdArchiveFileName = "ccd.tar.gz"
|
|
indexTxtDateLayout = "060102150405Z"
|
|
stringDateFormat = "2006-01-02 15:04:05"
|
|
downloadCertsApiUrl = "api/data/certs/download"
|
|
downloadCcdApiUrl = "api/data/ccd/download"
|
|
|
|
kubeNamespaceFilePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
|
|
)
|
|
|
|
var (
|
|
listenHost = kingpin.Flag("listen.host", "host for ovpn-admin").Default("0.0.0.0").Envar("OVPN_LISTEN_HOST").String()
|
|
listenPort = kingpin.Flag("listen.port", "port for ovpn-admin").Default("8080").Envar("OVPN_LISTEN_PORT").String()
|
|
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()
|
|
masterHost = kingpin.Flag("master.host", "URL for the master server").Default("http://127.0.0.1").Envar("OVPN_MASTER_HOST").String()
|
|
masterBasicAuthUser = kingpin.Flag("master.basic-auth.user", "user for master server's Basic Auth").Default("").Envar("OVPN_MASTER_USER").String()
|
|
masterBasicAuthPassword = kingpin.Flag("master.basic-auth.password", "password for master server's Basic Auth").Default("").Envar("OVPN_MASTER_PASSWORD").String()
|
|
masterSyncFrequency = kingpin.Flag("master.sync-frequency", "master host data sync frequency in seconds").Default("600").Envar("OVPN_MASTER_SYNC_FREQUENCY").Int()
|
|
masterSyncToken = kingpin.Flag("master.sync-token", "master host data sync security token").Default("VerySecureToken").Envar("OVPN_MASTER_TOKEN").PlaceHolder("TOKEN").String()
|
|
openvpnNetwork = kingpin.Flag("ovpn.network", "NETWORK/MASK_PREFIX for OpenVPN server").Default("172.16.100.0/24").Envar("OVPN_NETWORK").String()
|
|
openvpnServer = kingpin.Flag("ovpn.server", "HOST:PORT:PROTOCOL for OpenVPN server; can have multiple values").Default("127.0.0.1:7777:tcp").Envar("OVPN_SERVER").PlaceHolder("HOST:PORT:PROTOCOL").Strings()
|
|
openvpnServerBehindLB = kingpin.Flag("ovpn.server.behindLB", "enable if your OpenVPN server is behind Kubernetes Service having the LoadBalancer type").Default("false").Envar("OVPN_LB").Bool()
|
|
openvpnServiceName = kingpin.Flag("ovpn.service", "the name of Kubernetes Service having the LoadBalancer type if your OpenVPN server is behind it").Default("openvpn-external").Envar("OVPN_LB_SERVICE").Strings()
|
|
mgmtAddress = kingpin.Flag("mgmt", "ALIAS=HOST:PORT for OpenVPN server mgmt interface; can have multiple values").Default("main=127.0.0.1:8989").Envar("OVPN_MGMT").Strings()
|
|
metricsPath = kingpin.Flag("metrics.path", "URL path for exposing collected metrics").Default("/metrics").Envar("OVPN_METRICS_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()
|
|
easyrsaBinPath = kingpin.Flag("easyrsa.bin-path", "path to easyrsa script").Default("easyrsa").Envar("EASYRSA_BIN_PATH").String()
|
|
ccdEnabled = kingpin.Flag("ccd", "enable client-config-dir").Default("false").Envar("OVPN_CCD").Bool()
|
|
ccdDir = kingpin.Flag("ccd.path", "path to client-config-dir").Default("./ccd").Envar("OVPN_CCD_PATH").String()
|
|
clientConfigTemplatePath = kingpin.Flag("templates.clientconfig-path", "path to custom client.conf.tpl").Default("").Envar("OVPN_TEMPLATES_CC_PATH").String()
|
|
ccdTemplatePath = kingpin.Flag("templates.ccd-path", "path to custom ccd.tpl").Default("").Envar("OVPN_TEMPLATES_CCD_PATH").String()
|
|
authByPassword = kingpin.Flag("auth.password", "enable additional password authentication").Default("false").Envar("OVPN_AUTH").Bool()
|
|
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()
|
|
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()
|
|
|
|
certsArchivePath = "/tmp/" + certsArchiveFileName
|
|
ccdArchivePath = "/tmp/" + ccdArchiveFileName
|
|
|
|
version = "2.0.0"
|
|
)
|
|
|
|
var logLevels = map[string]log.Level{
|
|
"trace": log.TraceLevel,
|
|
"debug": log.DebugLevel,
|
|
"info": log.InfoLevel,
|
|
"warn": log.WarnLevel,
|
|
"error": log.ErrorLevel,
|
|
}
|
|
|
|
var logFormats = map[string]log.Formatter{
|
|
"text": &log.TextFormatter{},
|
|
"json": &log.JSONFormatter{},
|
|
}
|
|
|
|
var (
|
|
ovpnServerCertExpire = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "ovpn_server_cert_expire",
|
|
Help: "openvpn server certificate expire time in days",
|
|
},
|
|
)
|
|
|
|
ovpnServerCaCertExpire = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "ovpn_server_ca_cert_expire",
|
|
Help: "openvpn server CA certificate expire time in days",
|
|
},
|
|
)
|
|
|
|
ovpnClientsTotal = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "ovpn_clients_total",
|
|
Help: "total openvpn users",
|
|
},
|
|
)
|
|
|
|
ovpnClientsRevoked = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "ovpn_clients_revoked",
|
|
Help: "revoked openvpn users",
|
|
},
|
|
)
|
|
|
|
ovpnClientsExpired = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "ovpn_clients_expired",
|
|
Help: "expired openvpn users",
|
|
},
|
|
)
|
|
|
|
ovpnClientsConnected = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "ovpn_clients_connected",
|
|
Help: "total connected openvpn clients",
|
|
},
|
|
)
|
|
|
|
ovpnUniqClientsConnected = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "ovpn_uniq_clients_connected",
|
|
Help: "uniq connected openvpn clients",
|
|
},
|
|
)
|
|
|
|
ovpnClientCertificateExpire = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "ovpn_client_cert_expire",
|
|
Help: "openvpn user certificate expire time in days",
|
|
},
|
|
[]string{"client"},
|
|
)
|
|
|
|
ovpnClientConnectionInfo = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "ovpn_client_connection_info",
|
|
Help: "openvpn user connection info. ip - assigned address from ovpn network. value - last time when connection was refreshed in unix format",
|
|
},
|
|
[]string{"client", "ip"},
|
|
)
|
|
|
|
ovpnClientConnectionFrom = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "ovpn_client_connection_from",
|
|
Help: "openvpn user connection info. ip - from which address connection was initialized. value - time when connection was initialized in unix format",
|
|
},
|
|
[]string{"client", "ip"},
|
|
)
|
|
|
|
ovpnClientBytesReceived = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "ovpn_client_bytes_received",
|
|
Help: "openvpn user bytes received",
|
|
},
|
|
[]string{"client"},
|
|
)
|
|
|
|
ovpnClientBytesSent = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "ovpn_client_bytes_sent",
|
|
Help: "openvpn user bytes sent",
|
|
},
|
|
[]string{"client"},
|
|
)
|
|
)
|
|
|
|
type OvpnAdmin struct {
|
|
role string
|
|
lastSyncTime string
|
|
lastSuccessfulSyncTime string
|
|
masterHostBasicAuth bool
|
|
masterSyncToken string
|
|
clients []OpenvpnClient
|
|
activeClients []clientStatus
|
|
promRegistry *prometheus.Registry
|
|
mgmtInterfaces map[string]string
|
|
templates *packr.Box
|
|
modules []string
|
|
mgmtStatusTimeFormat string
|
|
createUserMutex *sync.Mutex
|
|
}
|
|
|
|
type OpenvpnServer struct {
|
|
Host string
|
|
Port string
|
|
Protocol string
|
|
}
|
|
|
|
type openvpnClientConfig struct {
|
|
Hosts []OpenvpnServer
|
|
CA string
|
|
Cert string
|
|
Key string
|
|
TLS string
|
|
PasswdAuth bool
|
|
}
|
|
|
|
type OpenvpnClient struct {
|
|
Identity string `json:"Identity"`
|
|
AccountStatus string `json:"AccountStatus"`
|
|
ExpirationDate string `json:"ExpirationDate"`
|
|
RevocationDate string `json:"RevocationDate"`
|
|
ConnectionStatus string `json:"ConnectionStatus"`
|
|
Connections int `json:"Connections"`
|
|
}
|
|
|
|
type ccdRoute struct {
|
|
Address string `json:"Address"`
|
|
Mask string `json:"Mask"`
|
|
Description string `json:"Description"`
|
|
}
|
|
|
|
type Ccd struct {
|
|
User string `json:"User"`
|
|
ClientAddress string `json:"ClientAddress"`
|
|
CustomRoutes []ccdRoute `json:"CustomRoutes"`
|
|
}
|
|
|
|
type indexTxtLine struct {
|
|
Flag string
|
|
ExpirationDate string
|
|
RevocationDate string
|
|
SerialNumber string
|
|
Filename string
|
|
DistinguishedName string
|
|
Identity string
|
|
}
|
|
|
|
type clientStatus struct {
|
|
CommonName string
|
|
RealAddress string
|
|
BytesReceived string
|
|
BytesSent string
|
|
ConnectedSince string
|
|
VirtualAddress string
|
|
LastRef string
|
|
ConnectedSinceFormatted string
|
|
LastRefFormatted string
|
|
ConnectedTo string
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userListHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
err := app.updateIndexTxtOnDisk()
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
}
|
|
oAdmin.clients = oAdmin.usersList()
|
|
}
|
|
|
|
usersList, _ := json.Marshal(oAdmin.clients)
|
|
fmt.Fprintf(w, "%s", usersList)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userStatisticHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
_ = r.ParseForm()
|
|
userStatistic, _ := json.Marshal(oAdmin.getUserStatistic(r.FormValue("username")))
|
|
fmt.Fprintf(w, "%s", userStatistic)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userCreateHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
if oAdmin.role == "slave" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusLocked)
|
|
return
|
|
}
|
|
_ = r.ParseForm()
|
|
userCreated, userCreateStatus := oAdmin.userCreate(r.FormValue("username"), r.FormValue("password"))
|
|
|
|
if userCreated {
|
|
oAdmin.clients = oAdmin.usersList()
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, userCreateStatus)
|
|
return
|
|
} else {
|
|
http.Error(w, userCreateStatus, http.StatusUnprocessableEntity)
|
|
}
|
|
}
|
|
func (oAdmin *OvpnAdmin) userRotateHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
if oAdmin.role == "slave" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusLocked)
|
|
return
|
|
}
|
|
_ = r.ParseForm()
|
|
err, msg := oAdmin.userRotate(r.FormValue("username"), r.FormValue("password"))
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
} else {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, msg)
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
if oAdmin.role == "slave" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusLocked)
|
|
return
|
|
}
|
|
_ = r.ParseForm()
|
|
err, msg := oAdmin.userDelete(r.FormValue("username"))
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
} else {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, msg)
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userRevokeHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
if oAdmin.role == "slave" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusLocked)
|
|
return
|
|
}
|
|
_ = r.ParseForm()
|
|
err, msg := oAdmin.userRevoke(r.FormValue("username"))
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
} else {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, msg)
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userUnrevokeHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
if oAdmin.role == "slave" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusLocked)
|
|
return
|
|
}
|
|
_ = r.ParseForm()
|
|
err, msg := oAdmin.userUnrevoke(r.FormValue("username"))
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
} else {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, msg)
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
_ = r.ParseForm()
|
|
if *authByPassword {
|
|
err, msg := oAdmin.userChangePassword(r.FormValue("username"), r.FormValue("password"))
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprintf(w, `{"status":"error", "message": "%s"}`, msg)
|
|
|
|
} else {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, `{"status":"ok", "message": "%s"}`, msg)
|
|
}
|
|
} else {
|
|
http.Error(w, `{"status":"error"}`, http.StatusNotImplemented)
|
|
}
|
|
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userShowConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
_ = r.ParseForm()
|
|
fmt.Fprintf(w, "%s", oAdmin.renderClientConfig(r.FormValue("username")))
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userDisconnectHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
_ = r.ParseForm()
|
|
// fmt.Fprintf(w, "%s", userDisconnect(r.FormValue("username")))
|
|
fmt.Fprintf(w, "%s", r.FormValue("username"))
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userShowCcdHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
_ = r.ParseForm()
|
|
ccd, _ := json.Marshal(oAdmin.getCcd(r.FormValue("username")))
|
|
fmt.Fprintf(w, "%s", ccd)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userApplyCcdHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
if oAdmin.role == "slave" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusLocked)
|
|
return
|
|
}
|
|
var ccd Ccd
|
|
if r.Body == nil {
|
|
http.Error(w, "Please send a request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&ccd)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
}
|
|
|
|
ccdApplied, applyStatus := oAdmin.modifyCcd(ccd)
|
|
|
|
if ccdApplied {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, applyStatus)
|
|
return
|
|
} else {
|
|
http.Error(w, applyStatus, http.StatusUnprocessableEntity)
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) serverSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
enabledModules, enabledModulesErr := json.Marshal(oAdmin.modules)
|
|
if enabledModulesErr != nil {
|
|
log.Errorln(enabledModulesErr)
|
|
}
|
|
fmt.Fprintf(w, `{"status":"ok", "serverRole": "%s", "modules": %s }`, oAdmin.role, string(enabledModules))
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) lastSyncTimeHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Debug(r.RemoteAddr, " ", r.RequestURI)
|
|
fmt.Fprint(w, oAdmin.lastSyncTime)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) lastSuccessfulSyncTimeHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Debug(r.RemoteAddr, " ", r.RequestURI)
|
|
fmt.Fprint(w, oAdmin.lastSuccessfulSyncTime)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) downloadCertsHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
if oAdmin.role == "slave" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
_ = r.ParseForm()
|
|
token := r.Form.Get("token")
|
|
|
|
if token != oAdmin.masterSyncToken {
|
|
http.Error(w, `{"status":"error"}`, http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
archiveCerts()
|
|
w.Header().Set("Content-Disposition", "attachment; filename="+certsArchiveFileName)
|
|
http.ServeFile(w, r, certsArchivePath)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) downloadCcdHandler(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(r.RemoteAddr, " ", r.RequestURI)
|
|
if oAdmin.role == "slave" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
http.Error(w, `{"status":"error"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
_ = r.ParseForm()
|
|
token := r.Form.Get("token")
|
|
|
|
if token != oAdmin.masterSyncToken {
|
|
http.Error(w, `{"status":"error"}`, http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
archiveCcd()
|
|
w.Header().Set("Content-Disposition", "attachment; filename="+ccdArchiveFileName)
|
|
http.ServeFile(w, r, ccdArchivePath)
|
|
}
|
|
|
|
var app OpenVPNPKI
|
|
|
|
func main() {
|
|
kingpin.Version(version)
|
|
kingpin.Parse()
|
|
|
|
log.SetLevel(logLevels[*logLevel])
|
|
log.SetFormatter(logFormats[*logFormat])
|
|
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
err := app.run()
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|
|
|
|
if *indexTxtPath == "" {
|
|
*indexTxtPath = *easyrsaDirPath + "/pki/index.txt"
|
|
}
|
|
|
|
ovpnAdmin := new(OvpnAdmin)
|
|
|
|
ovpnAdmin.lastSyncTime = "unknown"
|
|
ovpnAdmin.role = *serverRole
|
|
ovpnAdmin.lastSuccessfulSyncTime = "unknown"
|
|
ovpnAdmin.masterSyncToken = *masterSyncToken
|
|
ovpnAdmin.promRegistry = prometheus.NewRegistry()
|
|
ovpnAdmin.modules = []string{}
|
|
ovpnAdmin.createUserMutex = &sync.Mutex{}
|
|
ovpnAdmin.mgmtInterfaces = make(map[string]string)
|
|
|
|
for _, mgmtInterface := range *mgmtAddress {
|
|
parts := strings.SplitN(mgmtInterface, "=", 2)
|
|
ovpnAdmin.mgmtInterfaces[parts[0]] = parts[len(parts)-1]
|
|
}
|
|
|
|
ovpnAdmin.mgmtSetTimeFormat()
|
|
|
|
ovpnAdmin.registerMetrics()
|
|
ovpnAdmin.setState()
|
|
|
|
go ovpnAdmin.updateState()
|
|
|
|
if *masterBasicAuthPassword != "" && *masterBasicAuthUser != "" {
|
|
ovpnAdmin.masterHostBasicAuth = true
|
|
} else {
|
|
ovpnAdmin.masterHostBasicAuth = false
|
|
}
|
|
|
|
ovpnAdmin.modules = append(ovpnAdmin.modules, "core")
|
|
|
|
if *authByPassword {
|
|
if *storageBackend != "kubernetes.secrets" {
|
|
ovpnAdmin.modules = append(ovpnAdmin.modules, "passwdAuth")
|
|
} else {
|
|
log.Fatal("Right now the keys `--storage.backend=kubernetes.secret` and `--auth.password` are not working together. Please use only one of them ")
|
|
}
|
|
}
|
|
|
|
if *ccdEnabled {
|
|
ovpnAdmin.modules = append(ovpnAdmin.modules, "ccd")
|
|
}
|
|
|
|
if ovpnAdmin.role == "slave" {
|
|
ovpnAdmin.syncDataFromMaster()
|
|
go ovpnAdmin.syncWithMaster()
|
|
}
|
|
|
|
ovpnAdmin.templates = packr.New("template", "./templates")
|
|
|
|
staticBox := packr.New("static", "./frontend/static")
|
|
static := CacheControlWrapper(http.FileServer(staticBox))
|
|
|
|
http.Handle(*listenBaseUrl, http.StripPrefix(strings.TrimRight(*listenBaseUrl, "/"), static))
|
|
http.HandleFunc(*listenBaseUrl + "api/server/settings", ovpnAdmin.serverSettingsHandler)
|
|
http.HandleFunc(*listenBaseUrl + "api/users/list", ovpnAdmin.userListHandler)
|
|
http.HandleFunc(*listenBaseUrl + "api/user/create", ovpnAdmin.userCreateHandler)
|
|
http.HandleFunc(*listenBaseUrl + "api/user/change-password", ovpnAdmin.userChangePasswordHandler)
|
|
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)
|
|
http.HandleFunc(*listenBaseUrl + "api/user/ccd", ovpnAdmin.userShowCcdHandler)
|
|
http.HandleFunc(*listenBaseUrl + "api/user/ccd/apply", ovpnAdmin.userApplyCcdHandler)
|
|
|
|
http.HandleFunc(*listenBaseUrl + "api/sync/last/try", ovpnAdmin.lastSyncTimeHandler)
|
|
http.HandleFunc(*listenBaseUrl + "api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler)
|
|
http.HandleFunc(*listenBaseUrl + downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler)
|
|
http.HandleFunc(*listenBaseUrl + downloadCcdApiUrl, ovpnAdmin.downloadCcdHandler)
|
|
|
|
http.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{}))
|
|
http.HandleFunc(*listenBaseUrl + "ping", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "pong")
|
|
})
|
|
|
|
log.Printf("Bind: http://%s:%s%s", *listenHost, *listenPort, *listenBaseUrl)
|
|
log.Fatal(http.ListenAndServe(*listenHost+":"+*listenPort, nil))
|
|
}
|
|
|
|
func CacheControlWrapper(h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Cache-Control", "max-age=2592000") // 30 days
|
|
h.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) registerMetrics() {
|
|
oAdmin.promRegistry.MustRegister(ovpnServerCertExpire)
|
|
oAdmin.promRegistry.MustRegister(ovpnServerCaCertExpire)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientsTotal)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientsRevoked)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientsConnected)
|
|
oAdmin.promRegistry.MustRegister(ovpnUniqClientsConnected)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientsExpired)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientCertificateExpire)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientConnectionInfo)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientConnectionFrom)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientBytesReceived)
|
|
oAdmin.promRegistry.MustRegister(ovpnClientBytesSent)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) setState() {
|
|
oAdmin.activeClients = oAdmin.mgmtGetActiveClients()
|
|
oAdmin.clients = oAdmin.usersList()
|
|
|
|
ovpnServerCaCertExpire.Set(float64((getOvpnCaCertExpireDate().Unix() - time.Now().Unix()) / 3600 / 24))
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) updateState() {
|
|
for {
|
|
time.Sleep(time.Duration(28) * time.Second)
|
|
ovpnClientBytesSent.Reset()
|
|
ovpnClientBytesReceived.Reset()
|
|
ovpnClientConnectionFrom.Reset()
|
|
ovpnClientConnectionInfo.Reset()
|
|
ovpnClientCertificateExpire.Reset()
|
|
go oAdmin.setState()
|
|
}
|
|
}
|
|
|
|
func indexTxtParser(txt string) []indexTxtLine {
|
|
var indexTxt []indexTxtLine
|
|
|
|
txtLinesArray := strings.Split(txt, "\n")
|
|
|
|
for _, v := range txtLinesArray {
|
|
str := strings.Fields(v)
|
|
if len(str) > 0 {
|
|
switch {
|
|
// case strings.HasPrefix(str[0], "E"):
|
|
case strings.HasPrefix(str[0], "V"):
|
|
indexTxt = append(indexTxt, indexTxtLine{Flag: str[0], ExpirationDate: str[1], SerialNumber: str[2], Filename: str[3], DistinguishedName: str[4], Identity: str[4][strings.Index(str[4], "=")+1:]})
|
|
case strings.HasPrefix(str[0], "R"):
|
|
indexTxt = append(indexTxt, indexTxtLine{Flag: str[0], ExpirationDate: str[1], RevocationDate: str[2], SerialNumber: str[3], Filename: str[4], DistinguishedName: str[5], Identity: str[5][strings.Index(str[5], "=")+1:]})
|
|
}
|
|
}
|
|
}
|
|
|
|
return indexTxt
|
|
}
|
|
|
|
func renderIndexTxt(data []indexTxtLine) string {
|
|
indexTxt := ""
|
|
for _, line := range data {
|
|
switch {
|
|
case line.Flag == "V":
|
|
indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", line.Flag, line.ExpirationDate, line.SerialNumber, line.Filename, line.DistinguishedName)
|
|
case line.Flag == "R":
|
|
indexTxt += fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", line.Flag, line.ExpirationDate, line.RevocationDate, line.SerialNumber, line.Filename, line.DistinguishedName)
|
|
// case line.flag == "E":
|
|
}
|
|
}
|
|
return indexTxt
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) getClientConfigTemplate() *template.Template {
|
|
if *clientConfigTemplatePath != "" {
|
|
return template.Must(template.ParseFiles(*clientConfigTemplatePath))
|
|
} else {
|
|
clientConfigTpl, clientConfigTplErr := oAdmin.templates.FindString("client.conf.tpl")
|
|
if clientConfigTplErr != nil {
|
|
log.Error("clientConfigTpl not found in templates box")
|
|
}
|
|
return template.Must(template.New("client-config").Parse(clientConfigTpl))
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) renderClientConfig(username string) string {
|
|
if checkUserExist(username) {
|
|
var hosts []OpenvpnServer
|
|
|
|
for _, server := range *openvpnServer {
|
|
parts := strings.SplitN(server, ":", 3)
|
|
hosts = append(hosts, OpenvpnServer{Host: parts[0], Port: parts[1], Protocol: parts[2]})
|
|
}
|
|
|
|
if *openvpnServerBehindLB {
|
|
var err error
|
|
hosts, err = getOvpnServerHostsFromKubeApi()
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|
|
|
|
log.Tracef("hosts for %s\n %v", username, hosts)
|
|
|
|
conf := openvpnClientConfig{}
|
|
conf.Hosts = hosts
|
|
conf.CA = fRead(*easyrsaDirPath + "/pki/ca.crt")
|
|
conf.TLS = fRead(*easyrsaDirPath + "/pki/ta.key")
|
|
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
conf.Cert, conf.Key = app.easyrsaGetClientCert(username)
|
|
} else {
|
|
conf.Cert = fRead(*easyrsaDirPath + "/pki/issued/" + username + ".crt")
|
|
conf.Key = fRead(*easyrsaDirPath + "/pki/private/" + username + ".key")
|
|
}
|
|
|
|
conf.PasswdAuth = *authByPassword
|
|
|
|
t := oAdmin.getClientConfigTemplate()
|
|
|
|
var tmp bytes.Buffer
|
|
err := t.Execute(&tmp, conf)
|
|
if err != nil {
|
|
log.Errorf("something goes wrong during rendering config for %s", username)
|
|
log.Debugf("rendering config for %s failed with error %v", username, err)
|
|
}
|
|
|
|
hosts = nil
|
|
|
|
log.Tracef("Rendered config for user %s: %+v", username, tmp.String())
|
|
|
|
return fmt.Sprintf("%+v", tmp.String())
|
|
}
|
|
log.Warnf("user \"%s\" not found", username)
|
|
return fmt.Sprintf("user \"%s\" not found", username)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) getCcdTemplate() *template.Template {
|
|
if *ccdTemplatePath != "" {
|
|
return template.Must(template.ParseFiles(*ccdTemplatePath))
|
|
} else {
|
|
ccdTpl, ccdTplErr := oAdmin.templates.FindString("ccd.tpl")
|
|
if ccdTplErr != nil {
|
|
log.Errorf("ccdTpl not found in templates box")
|
|
}
|
|
return template.Must(template.New("ccd").Parse(ccdTpl))
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) parseCcd(username string) Ccd {
|
|
ccd := Ccd{}
|
|
ccd.User = username
|
|
ccd.ClientAddress = "dynamic"
|
|
ccd.CustomRoutes = []ccdRoute{}
|
|
|
|
var txtLinesArray []string
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
txtLinesArray = strings.Split(app.secretGetCcd(ccd.User), "\n")
|
|
} else {
|
|
if fExist(*ccdDir + "/" + username) {
|
|
txtLinesArray = strings.Split(fRead(*ccdDir+"/"+username), "\n")
|
|
}
|
|
}
|
|
|
|
for _, v := range txtLinesArray {
|
|
str := strings.Fields(v)
|
|
if len(str) > 0 {
|
|
switch {
|
|
case strings.HasPrefix(str[0], "ifconfig-push"):
|
|
ccd.ClientAddress = str[1]
|
|
case strings.HasPrefix(str[0], "push"):
|
|
ccd.CustomRoutes = append(ccd.CustomRoutes, ccdRoute{Address: strings.Trim(str[2], "\""), Mask: strings.Trim(str[3], "\""), Description: strings.Trim(strings.Join(str[4:], ""), "#")})
|
|
}
|
|
}
|
|
}
|
|
|
|
return ccd
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) modifyCcd(ccd Ccd) (bool, string) {
|
|
ccdValid, err := validateCcd(ccd)
|
|
if err != "" {
|
|
return false, err
|
|
}
|
|
|
|
if ccdValid {
|
|
t := oAdmin.getCcdTemplate()
|
|
var tmp bytes.Buffer
|
|
err := t.Execute(&tmp, ccd)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
app.secretUpdateCcd(ccd.User, tmp.Bytes())
|
|
} else {
|
|
err = fWrite(*ccdDir+"/"+ccd.User, tmp.String())
|
|
if err != nil {
|
|
log.Errorf("modifyCcd: fWrite(): %v", err)
|
|
}
|
|
}
|
|
|
|
return true, "ccd updated successfully"
|
|
}
|
|
|
|
return false, "something goes wrong"
|
|
}
|
|
|
|
func validateCcd(ccd Ccd) (bool, string) {
|
|
|
|
ccdErr := ""
|
|
|
|
if ccd.ClientAddress != "dynamic" {
|
|
_, ovpnNet, err := net.ParseCIDR(*openvpnNetwork)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
|
|
if !checkStaticAddressIsFree(ccd.ClientAddress, ccd.User) {
|
|
ccdErr = fmt.Sprintf("ClientAddress \"%s\" already assigned to another user", ccd.ClientAddress)
|
|
log.Debugf("modify ccd for user %s: %s", ccd.User, ccdErr)
|
|
return false, ccdErr
|
|
}
|
|
|
|
if net.ParseIP(ccd.ClientAddress) == nil {
|
|
ccdErr = fmt.Sprintf("ClientAddress \"%s\" not a valid IP address", ccd.ClientAddress)
|
|
log.Debugf("modify ccd for user %s: %s", ccd.User, ccdErr)
|
|
return false, ccdErr
|
|
}
|
|
|
|
if !ovpnNet.Contains(net.ParseIP(ccd.ClientAddress)) {
|
|
ccdErr = fmt.Sprintf("ClientAddress \"%s\" not belongs to openvpn server network", ccd.ClientAddress)
|
|
log.Debugf("modify ccd for user %s: %s", ccd.User, ccdErr)
|
|
return false, ccdErr
|
|
}
|
|
}
|
|
|
|
for _, route := range ccd.CustomRoutes {
|
|
if net.ParseIP(route.Address) == nil {
|
|
ccdErr = fmt.Sprintf("CustomRoute.Address \"%s\" must be a valid IP address", route.Address)
|
|
log.Debugf("modify ccd for user %s: %s", ccd.User, ccdErr)
|
|
return false, ccdErr
|
|
}
|
|
|
|
if net.ParseIP(route.Mask) == nil {
|
|
ccdErr = fmt.Sprintf("CustomRoute.Mask \"%s\" must be a valid IP address", route.Mask)
|
|
log.Debugf("modify ccd for user %s: %s", ccd.User, ccdErr)
|
|
return false, ccdErr
|
|
}
|
|
}
|
|
|
|
return true, ccdErr
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) getCcd(username string) Ccd {
|
|
ccd := Ccd{}
|
|
ccd.User = username
|
|
ccd.ClientAddress = "dynamic"
|
|
ccd.CustomRoutes = []ccdRoute{}
|
|
|
|
ccd = oAdmin.parseCcd(username)
|
|
|
|
return ccd
|
|
}
|
|
|
|
func checkStaticAddressIsFree(staticAddress string, username string) bool {
|
|
o := runBash(fmt.Sprintf("grep -rl ' %[1]s ' %[2]s | grep -vx %[2]s/%[3]s | wc -l", staticAddress, *ccdDir, username))
|
|
|
|
if strings.TrimSpace(o) == "0" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func validateUsername(username string) error {
|
|
var validUsername = regexp.MustCompile(usernameRegexp)
|
|
if validUsername.MatchString(username) {
|
|
return nil
|
|
} else {
|
|
return errors.New(fmt.Sprintf("Username can only contains %s", usernameRegexp))
|
|
}
|
|
}
|
|
|
|
func validatePassword(password string) error {
|
|
if utf8.RuneCountInString(password) < passwordMinLength {
|
|
return errors.New(fmt.Sprintf("Password too short, password length must be greater or equal %d", passwordMinLength))
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func checkUserExist(username string) bool {
|
|
for _, u := range indexTxtParser(fRead(*indexTxtPath)) {
|
|
if u.DistinguishedName == ("/CN=" + username) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) usersList() []OpenvpnClient {
|
|
var users []OpenvpnClient
|
|
|
|
totalCerts := 0
|
|
validCerts := 0
|
|
revokedCerts := 0
|
|
expiredCerts := 0
|
|
connectedUniqUsers := 0
|
|
totalActiveConnections := 0
|
|
apochNow := time.Now().Unix()
|
|
|
|
for _, line := range indexTxtParser(fRead(*indexTxtPath)) {
|
|
if line.Identity != "server" && !strings.Contains(line.Identity, "REVOKED") {
|
|
totalCerts += 1
|
|
ovpnClient := OpenvpnClient{Identity: line.Identity, ExpirationDate: parseDateToString(indexTxtDateLayout, line.ExpirationDate, stringDateFormat)}
|
|
switch {
|
|
case line.Flag == "V":
|
|
ovpnClient.AccountStatus = "Active"
|
|
validCerts += 1
|
|
case line.Flag == "R":
|
|
ovpnClient.AccountStatus = "Revoked"
|
|
ovpnClient.RevocationDate = parseDateToString(indexTxtDateLayout, line.RevocationDate, stringDateFormat)
|
|
revokedCerts += 1
|
|
case line.Flag == "E":
|
|
ovpnClient.AccountStatus = "Expired"
|
|
expiredCerts += 1
|
|
}
|
|
|
|
ovpnClientCertificateExpire.WithLabelValues(line.Identity).Set(float64((parseDateToUnix(indexTxtDateLayout, line.ExpirationDate) - apochNow) / 3600 / 24))
|
|
|
|
if (parseDateToUnix(indexTxtDateLayout, line.ExpirationDate) - apochNow) < 0 {
|
|
ovpnClient.AccountStatus = "Expired"
|
|
}
|
|
ovpnClient.Connections = 0
|
|
|
|
userConnected, userConnectedTo := isUserConnected(line.Identity, oAdmin.activeClients)
|
|
if userConnected {
|
|
ovpnClient.ConnectionStatus = "Connected"
|
|
for range userConnectedTo {
|
|
ovpnClient.Connections += 1
|
|
totalActiveConnections += 1
|
|
}
|
|
connectedUniqUsers += 1
|
|
}
|
|
|
|
users = append(users, ovpnClient)
|
|
|
|
} else {
|
|
ovpnServerCertExpire.Set(float64((parseDateToUnix(indexTxtDateLayout, line.ExpirationDate) - apochNow) / 3600 / 24))
|
|
}
|
|
}
|
|
|
|
otherCerts := totalCerts - validCerts - revokedCerts - expiredCerts
|
|
|
|
if otherCerts != 0 {
|
|
log.Warnf("there are %d otherCerts", otherCerts)
|
|
}
|
|
|
|
ovpnClientsTotal.Set(float64(totalCerts))
|
|
ovpnClientsRevoked.Set(float64(revokedCerts))
|
|
ovpnClientsExpired.Set(float64(expiredCerts))
|
|
ovpnClientsConnected.Set(float64(totalActiveConnections))
|
|
ovpnUniqClientsConnected.Set(float64(connectedUniqUsers))
|
|
|
|
return users
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userCreate(username, password string) (bool, string) {
|
|
ucErr := fmt.Sprintf("User \"%s\" created", username)
|
|
|
|
oAdmin.createUserMutex.Lock()
|
|
defer oAdmin.createUserMutex.Unlock()
|
|
|
|
if checkUserExist(username) {
|
|
ucErr = fmt.Sprintf("User \"%s\" already exists\n", username)
|
|
log.Debugf("userCreate: checkUserExist(): %s", ucErr)
|
|
return false, ucErr
|
|
}
|
|
|
|
if err := validateUsername(username); err != nil {
|
|
log.Debugf("userCreate: validateUsername(): %s", err.Error())
|
|
return false, err.Error()
|
|
}
|
|
|
|
if *authByPassword {
|
|
if err := validatePassword(password); err != nil {
|
|
log.Debugf("userCreate: authByPassword(): %s", err.Error())
|
|
return false, err.Error()
|
|
}
|
|
}
|
|
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
err := app.easyrsaBuildClient(username)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
} else {
|
|
o := runBash(fmt.Sprintf("cd %s && %s build-client-full %s nopass 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath, username))
|
|
log.Debug(o)
|
|
}
|
|
|
|
if *authByPassword {
|
|
o := runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password))
|
|
log.Debug(o)
|
|
}
|
|
|
|
log.Infof("Certificate for user %s issued", username)
|
|
|
|
//oAdmin.clients = oAdmin.usersList()
|
|
|
|
return true, ucErr
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userChangePassword(username, password string) (error, string) {
|
|
|
|
if checkUserExist(username) {
|
|
o := runBash(fmt.Sprintf("openvpn-user check --db.path %[1]s --user %[2]s | grep %[2]s | wc -l", *authDatabase, username))
|
|
log.Debug(o)
|
|
|
|
if err := validatePassword(password); err != nil {
|
|
log.Warningf("userChangePassword: %s", err.Error())
|
|
return err, err.Error()
|
|
}
|
|
|
|
if strings.TrimSpace(o) == "0" {
|
|
o = runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password))
|
|
log.Debug(o)
|
|
}
|
|
|
|
o = runBash(fmt.Sprintf("openvpn-user change-password --db.path %s --user %s --password %s", *authDatabase, username, password))
|
|
log.Debug(o)
|
|
|
|
log.Infof("Password for user %s was changed", username)
|
|
|
|
return nil, "Password changed"
|
|
}
|
|
|
|
return errors.New(fmt.Sprintf("User \"%s\" not found}", username)), fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) getUserStatistic(username string) []clientStatus {
|
|
var userStatistic []clientStatus
|
|
for _, u := range oAdmin.activeClients {
|
|
if u.CommonName == username {
|
|
userStatistic = append(userStatistic, u)
|
|
}
|
|
}
|
|
return userStatistic
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
|
|
log.Infof("Revoke certificate for user %s", username)
|
|
if checkUserExist(username) {
|
|
// check certificate valid flag 'V'
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
err := app.easyrsaRevoke(username)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
} else {
|
|
o := runBash(fmt.Sprintf("cd %[1]s && echo yes | %[2]s revoke %[3]s 1>/dev/null && %[2]s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath, username))
|
|
log.Debugln(o)
|
|
}
|
|
|
|
if *authByPassword {
|
|
o := runBash(fmt.Sprintf("openvpn-user revoke --db-path %s --user %s", *authDatabase, username))
|
|
log.Debug(o)
|
|
}
|
|
|
|
crlFix()
|
|
userConnected, userConnectedTo := isUserConnected(username, oAdmin.activeClients)
|
|
log.Tracef("User %s connected: %t", username, userConnected)
|
|
if userConnected {
|
|
for _, connection := range userConnectedTo {
|
|
oAdmin.mgmtKillUserConnection(username, connection)
|
|
log.Infof("Session for user \"%s\" killed", username)
|
|
}
|
|
}
|
|
|
|
oAdmin.setState()
|
|
return nil, fmt.Sprintf("user \"%s\" revoked", username)
|
|
}
|
|
log.Infof("user \"%s\" not found", username)
|
|
return errors.New(fmt.Sprintf("User \"%s\" not found}", username)), fmt.Sprintf("User \"%s\" not found", username)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) {
|
|
if checkUserExist(username) {
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
err := app.easyrsaUnrevoke(username)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
} else {
|
|
// check certificate revoked flag 'R'
|
|
usersFromIndexTxt := indexTxtParser(fRead(*indexTxtPath))
|
|
for i := range usersFromIndexTxt {
|
|
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username {
|
|
if usersFromIndexTxt[i].Flag == "R" {
|
|
|
|
usersFromIndexTxt[i].Flag = "V"
|
|
usersFromIndexTxt[i].RevocationDate = ""
|
|
|
|
err := fMove(fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *easyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/issued/%s.crt", *easyrsaDirPath, username))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
err = fMove(fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *easyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/certs_by_serial/%s.pem", *easyrsaDirPath, usersFromIndexTxt[i].SerialNumber))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
err = fMove(fmt.Sprintf("%s/pki/revoked/private_by_serial/%s.key", *easyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/private/%s.key", *easyrsaDirPath, username))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
err = fMove(fmt.Sprintf("%s/pki/revoked/reqs_by_serial/%s.req", *easyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/reqs/%s.req", *easyrsaDirPath, username))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
err = fWrite(*indexTxtPath, renderIndexTxt(usersFromIndexTxt))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
|
|
_ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath))
|
|
|
|
if *authByPassword {
|
|
o := runBash(fmt.Sprintf("openvpn-user restore --db-path %s --user %s", *authDatabase, username))
|
|
log.Debug(o)
|
|
}
|
|
|
|
crlFix()
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
err := fWrite(*indexTxtPath, renderIndexTxt(usersFromIndexTxt))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
//fmt.Print(renderIndexTxt(usersFromIndexTxt))
|
|
}
|
|
crlFix()
|
|
oAdmin.clients = oAdmin.usersList()
|
|
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully unrevoked\"}", username)
|
|
}
|
|
return errors.New(fmt.Sprintf("user \"%s\" not found", username)), fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userRotate(username, newPassword string) (error, string) {
|
|
if checkUserExist(username) {
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
err := app.easyrsaRotate(username, newPassword)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
} else {
|
|
|
|
var oldUserIndex, newUserIndex int
|
|
var oldUserSerial string
|
|
|
|
uniqHash := strings.Replace(uuid.New().String(), "-", "", -1)
|
|
|
|
usersFromIndexTxt := indexTxtParser(fRead(*indexTxtPath))
|
|
for i := range usersFromIndexTxt {
|
|
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username {
|
|
oldUserSerial = usersFromIndexTxt[i].SerialNumber
|
|
usersFromIndexTxt[i].DistinguishedName = "/CN=REVOKED-" + username + "-" + uniqHash
|
|
oldUserIndex = i
|
|
break
|
|
}
|
|
}
|
|
err := fWrite(*indexTxtPath, renderIndexTxt(usersFromIndexTxt))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
|
|
if *authByPassword {
|
|
o := runBash(fmt.Sprintf("openvpn-user delete --force --db.path %s --user %s", *authDatabase, username))
|
|
log.Debug(o)
|
|
}
|
|
|
|
userCreated, userCreateMessage := oAdmin.userCreate(username, newPassword)
|
|
if !userCreated {
|
|
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 errors.New(fmt.Sprintf("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 && %s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath))
|
|
}
|
|
crlFix()
|
|
oAdmin.clients = oAdmin.usersList()
|
|
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully rotated\"}", username)
|
|
}
|
|
return errors.New(fmt.Sprintf("user \"%s\" not found", username)), fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) userDelete(username string) (error, string) {
|
|
if checkUserExist(username) {
|
|
if *storageBackend == "kubernetes.secrets" {
|
|
err := app.easyrsaDelete(username)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
} else {
|
|
uniqHash := strings.Replace(uuid.New().String(), "-", "", -1)
|
|
usersFromIndexTxt := indexTxtParser(fRead(*indexTxtPath))
|
|
for i := range usersFromIndexTxt {
|
|
if usersFromIndexTxt[i].DistinguishedName == "/CN="+username {
|
|
usersFromIndexTxt[i].DistinguishedName = "/CN=REVOKED-" + username + "-" + uniqHash
|
|
break
|
|
}
|
|
}
|
|
if *authByPassword {
|
|
_ = runBash(fmt.Sprintf("openvpn-user delete --force --db.path %s --user %s", *authDatabase, username))
|
|
}
|
|
err := fWrite(*indexTxtPath, renderIndexTxt(usersFromIndexTxt))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
_ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null ", *easyrsaDirPath, *easyrsaBinPath))
|
|
}
|
|
crlFix()
|
|
oAdmin.clients = oAdmin.usersList()
|
|
return nil, fmt.Sprintf("{\"msg\":\"User %s successfully deleted\"}", username)
|
|
}
|
|
return errors.New(fmt.Sprintf("User \"%s\" not found}", username)), fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username)
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) mgmtRead(conn net.Conn) string {
|
|
recvData := make([]byte, 32768)
|
|
var out string
|
|
var n int
|
|
var err error
|
|
for {
|
|
n, err = conn.Read(recvData)
|
|
if n <= 0 || err != nil {
|
|
break
|
|
} else {
|
|
out += string(recvData[:n])
|
|
if strings.Contains(out, "type 'help' for more info") || strings.Contains(out, "END") || strings.Contains(out, "SUCCESS:") || strings.Contains(out, "ERROR:") {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) mgmtConnectedUsersParser(text, serverName string) []clientStatus {
|
|
var u []clientStatus
|
|
isClientList := false
|
|
isRouteTable := false
|
|
scanner := bufio.NewScanner(strings.NewReader(text))
|
|
for scanner.Scan() {
|
|
txt := scanner.Text()
|
|
if regexp.MustCompile(`^Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since$`).MatchString(txt) {
|
|
isClientList = true
|
|
continue
|
|
}
|
|
if regexp.MustCompile(`^ROUTING TABLE$`).MatchString(txt) {
|
|
isClientList = false
|
|
continue
|
|
}
|
|
if regexp.MustCompile(`^Virtual Address,Common Name,Real Address,Last Ref$`).MatchString(txt) {
|
|
isRouteTable = true
|
|
continue
|
|
}
|
|
if regexp.MustCompile(`^GLOBAL STATS$`).MatchString(txt) {
|
|
// isRouteTable = false // ineffectual assignment to isRouteTable (ineffassign)
|
|
break
|
|
}
|
|
if isClientList {
|
|
user := strings.Split(txt, ",")
|
|
|
|
userName := user[0]
|
|
userAddress := user[1]
|
|
userBytesReceived := user[2]
|
|
userBytesSent := user[3]
|
|
userConnectedSince := user[4]
|
|
|
|
userStatus := clientStatus{CommonName: userName, RealAddress: userAddress, BytesReceived: userBytesReceived, BytesSent: userBytesSent, ConnectedSince: userConnectedSince, ConnectedTo: serverName}
|
|
u = append(u, userStatus)
|
|
bytesSent, _ := strconv.Atoi(userBytesSent)
|
|
bytesReceive, _ := strconv.Atoi(userBytesReceived)
|
|
ovpnClientConnectionFrom.WithLabelValues(userName, userAddress).Set(float64(parseDateToUnix(oAdmin.mgmtStatusTimeFormat, userConnectedSince)))
|
|
ovpnClientBytesSent.WithLabelValues(userName).Set(float64(bytesSent))
|
|
ovpnClientBytesReceived.WithLabelValues(userName).Set(float64(bytesReceive))
|
|
}
|
|
if isRouteTable {
|
|
user := strings.Split(txt, ",")
|
|
for i := range u {
|
|
if u[i].CommonName == user[1] {
|
|
u[i].VirtualAddress = user[0]
|
|
u[i].LastRef = user[3]
|
|
ovpnClientConnectionInfo.WithLabelValues(user[1], user[0]).Set(float64(parseDateToUnix(oAdmin.mgmtStatusTimeFormat, user[3])))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return u
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) mgmtKillUserConnection(username, serverName string) {
|
|
conn, err := net.Dial("tcp", oAdmin.mgmtInterfaces[serverName])
|
|
if err != nil {
|
|
log.Errorf("openvpn mgmt interface for %s is not reachable by addr %s", serverName, oAdmin.mgmtInterfaces[serverName])
|
|
return
|
|
}
|
|
oAdmin.mgmtRead(conn) // read welcome message
|
|
conn.Write([]byte(fmt.Sprintf("kill %s\n", username)))
|
|
fmt.Printf("%v", oAdmin.mgmtRead(conn))
|
|
conn.Close()
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) mgmtGetActiveClients() []clientStatus {
|
|
var activeClients []clientStatus
|
|
|
|
for srv, addr := range oAdmin.mgmtInterfaces {
|
|
conn, err := net.Dial("tcp", addr)
|
|
if err != nil {
|
|
log.Warnf("openvpn mgmt interface for %s is not reachable by addr %s", srv, addr)
|
|
break
|
|
}
|
|
oAdmin.mgmtRead(conn) // read welcome message
|
|
conn.Write([]byte("status\n"))
|
|
activeClients = append(activeClients, oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn), srv)...)
|
|
conn.Close()
|
|
}
|
|
return activeClients
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) mgmtSetTimeFormat() {
|
|
// time format for version 2.5 and may be newer
|
|
oAdmin.mgmtStatusTimeFormat = "2006-01-02 15:04:05"
|
|
log.Debugf("mgmtStatusTimeFormat: %s", oAdmin.mgmtStatusTimeFormat)
|
|
|
|
type serverVersion struct {
|
|
name string
|
|
version string
|
|
}
|
|
|
|
var serverVersions []serverVersion
|
|
|
|
for srv, addr := range oAdmin.mgmtInterfaces {
|
|
|
|
var conn net.Conn
|
|
var err error
|
|
for connAttempt := 0; connAttempt < 10; connAttempt++ {
|
|
conn, err = net.Dial("tcp", addr)
|
|
if err == nil {
|
|
log.Debugf("mgmtSetTimeFormat: successful connection to %s/%s", srv, addr)
|
|
break
|
|
}
|
|
log.Warnf("mgmtSetTimeFormat: openvpn mgmt interface for %s is not reachable by addr %s", srv, addr)
|
|
time.Sleep(time.Duration(2) * time.Second)
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
oAdmin.mgmtRead(conn) // read welcome message
|
|
conn.Write([]byte("version\n"))
|
|
out := oAdmin.mgmtRead(conn)
|
|
conn.Close()
|
|
|
|
log.Trace(out)
|
|
|
|
for _, s := range strings.Split(out, "\n") {
|
|
if strings.Contains(s, "OpenVPN Version:") {
|
|
serverVersions = append(serverVersions, serverVersion{srv, strings.Split(s, " ")[3]})
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(serverVersions) == 0 {
|
|
return
|
|
}
|
|
|
|
firstVersion := serverVersions[0].version
|
|
|
|
if strings.HasPrefix(firstVersion, "2.4") {
|
|
oAdmin.mgmtStatusTimeFormat = time.ANSIC
|
|
log.Debugf("mgmtStatusTimeFormat changed: %s", oAdmin.mgmtStatusTimeFormat)
|
|
}
|
|
|
|
warn := ""
|
|
for _, v := range serverVersions {
|
|
if firstVersion != v.version {
|
|
warn = "mgmtSetTimeFormat: servers have different versions of openvpn, user connection status may not work"
|
|
log.Warn(warn)
|
|
break
|
|
}
|
|
}
|
|
|
|
if warn != "" {
|
|
for _, v := range serverVersions {
|
|
log.Infof("server name: %s, version: %s", v.name, v.version)
|
|
}
|
|
}
|
|
}
|
|
|
|
func isUserConnected(username string, connectedUsers []clientStatus) (bool, []string) {
|
|
var connections []string
|
|
var connected = false
|
|
|
|
for _, connectedUser := range connectedUsers {
|
|
if connectedUser.CommonName == username {
|
|
connected = true
|
|
connections = append(connections, connectedUser.ConnectedTo)
|
|
}
|
|
}
|
|
return connected, connections
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) downloadCerts() bool {
|
|
if fExist(certsArchivePath) {
|
|
err := fDelete(certsArchivePath)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|
|
|
|
err := fDownload(certsArchivePath, *masterHost+*listenBaseUrl+downloadCertsApiUrl+"?token="+oAdmin.masterSyncToken, oAdmin.masterHostBasicAuth)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) downloadCcd() bool {
|
|
if fExist(ccdArchivePath) {
|
|
err := fDelete(ccdArchivePath)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|
|
|
|
err := fDownload(ccdArchivePath, *masterHost+*listenBaseUrl+downloadCcdApiUrl+"?token="+oAdmin.masterSyncToken, oAdmin.masterHostBasicAuth)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func archiveCerts() {
|
|
err := createArchiveFromDir(*easyrsaDirPath+"/pki", certsArchivePath)
|
|
if err != nil {
|
|
log.Warnf("archiveCerts(): %s", err)
|
|
}
|
|
}
|
|
|
|
func archiveCcd() {
|
|
err := createArchiveFromDir(*ccdDir, ccdArchivePath)
|
|
if err != nil {
|
|
log.Warnf("archiveCcd(): %s", err)
|
|
}
|
|
}
|
|
|
|
func unArchiveCerts() {
|
|
if err := os.MkdirAll(*easyrsaDirPath+"/pki", 0755); err != nil {
|
|
log.Warnf("unArchiveCerts(): error creating pki dir: %s", err)
|
|
}
|
|
|
|
err := extractFromArchive(certsArchivePath, *easyrsaDirPath+"/pki")
|
|
if err != nil {
|
|
log.Warnf("unArchiveCerts: extractFromArchive() %s", err)
|
|
}
|
|
}
|
|
|
|
func unArchiveCcd() {
|
|
if err := os.MkdirAll(*ccdDir, 0755); err != nil {
|
|
log.Warnf("unArchiveCcd(): error creating ccd dir: %s", err)
|
|
}
|
|
|
|
err := extractFromArchive(ccdArchivePath, *ccdDir)
|
|
if err != nil {
|
|
log.Warnf("unArchiveCcd: extractFromArchive() %s", err)
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) syncDataFromMaster() {
|
|
retryCountMax := 3
|
|
certsDownloadFailed := true
|
|
ccdDownloadFailed := true
|
|
|
|
for certsDownloadRetries := 0; certsDownloadRetries < retryCountMax; certsDownloadRetries++ {
|
|
log.Infof("Downloading archive with certificates from master. Attempt %d", certsDownloadRetries)
|
|
if oAdmin.downloadCerts() {
|
|
certsDownloadFailed = false
|
|
log.Info("Decompressing archive with certificates from master")
|
|
unArchiveCerts()
|
|
log.Info("Decompression archive with certificates from master completed")
|
|
break
|
|
} else {
|
|
log.Warnf("Something goes wrong during downloading archive with certificates from master. Attempt %d", certsDownloadRetries)
|
|
}
|
|
}
|
|
|
|
for ccdDownloadRetries := 0; ccdDownloadRetries < retryCountMax; ccdDownloadRetries++ {
|
|
log.Infof("Downloading archive with ccd from master. Attempt %d", ccdDownloadRetries)
|
|
if oAdmin.downloadCcd() {
|
|
ccdDownloadFailed = false
|
|
log.Info("Decompressing archive with ccd from master")
|
|
unArchiveCcd()
|
|
log.Info("Decompression archive with ccd from master completed")
|
|
break
|
|
} else {
|
|
log.Warnf("Something goes wrong during downloading archive with ccd from master. Attempt %d", ccdDownloadRetries)
|
|
}
|
|
}
|
|
|
|
oAdmin.lastSyncTime = time.Now().Format(stringDateFormat)
|
|
if !ccdDownloadFailed && !certsDownloadFailed {
|
|
oAdmin.lastSuccessfulSyncTime = time.Now().Format(stringDateFormat)
|
|
}
|
|
}
|
|
|
|
func (oAdmin *OvpnAdmin) syncWithMaster() {
|
|
for {
|
|
time.Sleep(time.Duration(*masterSyncFrequency) * time.Second)
|
|
oAdmin.syncDataFromMaster()
|
|
}
|
|
}
|
|
|
|
func getOvpnServerHostsFromKubeApi() ([]OpenvpnServer, error) {
|
|
var hosts []OpenvpnServer
|
|
var lbHost string
|
|
|
|
config, err := rest.InClusterConfig()
|
|
if err != nil {
|
|
log.Errorf("%s", err.Error())
|
|
}
|
|
|
|
clientset, err := kubernetes.NewForConfig(config)
|
|
if err != nil {
|
|
log.Errorf("%s", err.Error())
|
|
}
|
|
|
|
for _, serviceName := range *openvpnServiceName {
|
|
service, err := clientset.CoreV1().Services(fRead(kubeNamespaceFilePath)).Get(context.TODO(), serviceName, metav1.GetOptions{})
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
|
|
log.Tracef("service from kube api %v", service)
|
|
log.Tracef("service.Status from kube api %v", service.Status)
|
|
log.Tracef("service.Status.LoadBalancer from kube api %v", service.Status.LoadBalancer)
|
|
|
|
lbIngress := service.Status.LoadBalancer.Ingress
|
|
if len(lbIngress) > 0 {
|
|
if lbIngress[0].Hostname != "" {
|
|
lbHost = lbIngress[0].Hostname
|
|
}
|
|
|
|
if lbIngress[0].IP != "" {
|
|
lbHost = lbIngress[0].IP
|
|
}
|
|
}
|
|
|
|
hosts = append(hosts, OpenvpnServer{lbHost, strconv.Itoa(int(service.Spec.Ports[0].Port)), strings.ToLower(string(service.Spec.Ports[0].Protocol))})
|
|
}
|
|
|
|
if len(hosts) == 0 {
|
|
return []OpenvpnServer{{Host: "kubernetes services not found"}}, err
|
|
}
|
|
|
|
return hosts, nil
|
|
}
|
|
|
|
func getOvpnCaCertExpireDate() time.Time {
|
|
caCertPath := *easyrsaDirPath + "/pki/ca.crt"
|
|
caCert, err := ioutil.ReadFile(caCertPath)
|
|
if err != nil {
|
|
log.Errorf("error read file %s: %s", caCertPath, err.Error())
|
|
}
|
|
|
|
certPem, _ := pem.Decode(caCert)
|
|
certPemBytes := certPem.Bytes
|
|
|
|
cert, err := x509.ParseCertificate(certPemBytes)
|
|
if err != nil {
|
|
log.Errorf("error parse certificate ca.crt: %s", err.Error())
|
|
return time.Now()
|
|
}
|
|
|
|
return cert.NotAfter
|
|
}
|
|
|
|
// https://community.openvpn.net/openvpn/ticket/623
|
|
func crlFix() {
|
|
err := os.Chmod(*easyrsaDirPath+"/pki", 0755)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
err = os.Chmod(*easyrsaDirPath+"/pki/crl.pem", 0644)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|