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
downloadCertsApiUrl = "/api/data/certs/download"
downloadCcdApiUrl = "/api/data/ccd/download"
certsArchiveFileName = "certs.tar.gz"
ccdArchiveFileName = "ccd.tar.gz"
indexTxtDateLayout = "060102150405Z"
stringDateFormat = "2006-01-02 15:04:05"
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 ( )
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 ( )
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 )
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 ( "/" , static )
http . HandleFunc ( "/api/server/settings" , ovpnAdmin . serverSettingsHandler )
http . HandleFunc ( "/api/users/list" , ovpnAdmin . userListHandler )
http . HandleFunc ( "/api/user/create" , ovpnAdmin . userCreateHandler )
http . HandleFunc ( "/api/user/change-password" , ovpnAdmin . userChangePasswordHandler )
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 . HandleFunc ( "/api/user/statistic" , ovpnAdmin . userStatisticHandler )
http . HandleFunc ( "/api/user/ccd" , ovpnAdmin . userShowCcdHandler )
http . HandleFunc ( "/api/user/ccd/apply" , ovpnAdmin . userApplyCcdHandler )
http . HandleFunc ( "/api/sync/last/try" , ovpnAdmin . lastSyncTimeHandler )
http . HandleFunc ( "/api/sync/last/successful" , ovpnAdmin . lastSuccessfulSyncTimeHandler )
http . HandleFunc ( downloadCertsApiUrl , ovpnAdmin . downloadCertsHandler )
http . HandleFunc ( downloadCcdApiUrl , ovpnAdmin . downloadCcdHandler )
http . Handle ( * metricsPath , promhttp . HandlerFor ( ovpnAdmin . promRegistry , promhttp . HandlerOpts { } ) )
http . HandleFunc ( "/ping" , func ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprintf ( w , "pong" )
} )
log . Printf ( "Bind: http://%s:%s" , * listenHost , * listenPort )
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 )