@ -6,7 +6,6 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
@ -23,6 +22,7 @@ import (
"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"
)
@ -44,37 +44,44 @@ const (
)
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 ( )
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" ) . String ( )
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 ( "./easyrsa/pki/index.txt " ) . 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 ( )
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" ) . String ( )
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 ( )
debug = kingpin . Flag ( "debug" , "enable debug mode " ) . Default ( "false " ) . Envar ( "OVPN_DEBUG" ) . Bool ( )
verbose = kingpin . Flag ( "verbose" , "enable verbose mode" ) . Default ( "false" ) . Envar ( "OVPN_VERBOSE " ) . 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 ( )
logLevel = kingpin . Flag ( "log.level" , "set log level (debug, info, warn, error) " ) . Default ( "info " ) . Envar ( "LOG_LEVEL" ) . String ( )
kubernetesBackend = kingpin . Flag ( "kubernetes.backend" , "use kubernetes secrets for store certificates " ) . Bool ( )
certsArchivePath = "/tmp/" + certsArchiveFileName
ccdArchivePath = "/tmp/" + ccdArchiveFileName
certsArchivePath = "/tmp/" + certsArchiveFileName
ccdArchivePath = "/tmp/" + ccdArchiveFileName
version = "1.7.5"
)
var logLevels = map [ string ] log . Level {
"debug" : log . DebugLevel ,
"info" : log . InfoLevel ,
"warn" : log . WarnLevel ,
"error" : log . ErrorLevel ,
}
var (
ovpnServerCertExpire = prometheus . NewGauge ( prometheus . GaugeOpts {
Name : "ovpn_server_cert_expire" ,
@ -178,14 +185,12 @@ type openvpnClientConfig struct {
}
type OpenvpnClient struct {
Identity string ` json:"Identity" `
AccountStatus string ` json:"AccountStatus" `
ExpirationDate string ` json:"ExpirationDate" `
RevocationDate string ` json:"RevocationDate" `
ConnectionStatus string ` json:"ConnectionStatus" `
ConnectionServer string ` json:"ConnectionServer" `
Identity string ` json:"Identity" `
AccountStatus string ` json:"AccountStatus" `
ExpirationDate string ` json:"ExpirationDate" `
RevocationDate string ` json:"RevocationDate" `
ConnectionStatus string ` json:"ConnectionStatus" `
ConnectionServer string ` json:"ConnectionServer" `
}
type ccdRoute struct {
@ -229,7 +234,7 @@ func (oAdmin *OvpnAdmin) userListHandler(w http.ResponseWriter, r *http.Request)
}
func ( oAdmin * OvpnAdmin ) userStatisticHandler ( w http . ResponseWriter , r * http . Request ) {
r . ParseForm ( )
_ = r . ParseForm ( )
userStatistic , _ := json . Marshal ( oAdmin . getUserStatistic ( r . FormValue ( "username" ) ) )
fmt . Fprintf ( w , "%s" , userStatistic )
}
@ -239,7 +244,7 @@ func (oAdmin *OvpnAdmin) userCreateHandler(w http.ResponseWriter, r *http.Reques
http . Error ( w , ` { "status":"error"} ` , http . StatusLocked )
return
}
r . ParseForm ( )
_ = r . ParseForm ( )
userCreated , userCreateStatus := oAdmin . userCreate ( r . FormValue ( "username" ) , r . FormValue ( "password" ) )
if userCreated {
@ -256,7 +261,7 @@ func (oAdmin *OvpnAdmin) userRevokeHandler(w http.ResponseWriter, r *http.Reques
http . Error ( w , ` { "status":"error"} ` , http . StatusLocked )
return
}
r . ParseForm ( )
_ = r . ParseForm ( )
fmt . Fprintf ( w , "%s" , oAdmin . userRevoke ( r . FormValue ( "username" ) ) )
}
@ -266,12 +271,12 @@ func (oAdmin *OvpnAdmin) userUnrevokeHandler(w http.ResponseWriter, r *http.Requ
return
}
r . ParseForm ( )
_ = r . ParseForm ( )
fmt . Fprintf ( w , "%s" , oAdmin . userUnrevoke ( r . FormValue ( "username" ) ) )
}
func ( oAdmin * OvpnAdmin ) userChangePasswordHandler ( w http . ResponseWriter , r * http . Request ) {
r . ParseForm ( )
_ = r . ParseForm ( )
if * authByPassword {
passwordChanged , passwordChangeMessage := oAdmin . userChangePassword ( r . FormValue ( "username" ) , r . FormValue ( "password" ) )
if passwordChanged {
@ -290,18 +295,18 @@ func (oAdmin *OvpnAdmin) userChangePasswordHandler(w http.ResponseWriter, r *htt
}
func ( oAdmin * OvpnAdmin ) userShowConfigHandler ( w http . ResponseWriter , r * http . Request ) {
r . ParseForm ( )
_ = r . ParseForm ( )
fmt . Fprintf ( w , "%s" , oAdmin . renderClientConfig ( r . FormValue ( "username" ) ) )
}
func ( oAdmin * OvpnAdmin ) userDisconnectHandler ( w http . ResponseWriter , r * http . Request ) {
r . ParseForm ( )
_ = 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 ) {
r . ParseForm ( )
_ = r . ParseForm ( )
ccd , _ := json . Marshal ( oAdmin . getCcd ( r . FormValue ( "username" ) ) )
fmt . Fprintf ( w , "%s" , ccd )
}
@ -319,7 +324,7 @@ func (oAdmin *OvpnAdmin) userApplyCcdHandler(w http.ResponseWriter, r *http.Requ
err := json . NewDecoder ( r . Body ) . Decode ( & ccd )
if err != nil {
log . Print ln( err )
log . Error ln( err )
}
ccdApplied , applyStatus := oAdmin . modifyCcd ( ccd )
@ -336,7 +341,7 @@ func (oAdmin *OvpnAdmin) userApplyCcdHandler(w http.ResponseWriter, r *http.Requ
func ( oAdmin * OvpnAdmin ) serverSettingsHandler ( w http . ResponseWriter , r * http . Request ) {
enabledModules , enabledModulesErr := json . Marshal ( oAdmin . modules )
if enabledModulesErr != nil {
log . Printf ( "ERROR: %s\n" , enabledModulesErr )
log . Errorln ( enabledModulesErr )
}
fmt . Fprintf ( w , ` { "status":"ok", "serverRole": "%s", "modules": %s } ` , oAdmin . role , string ( enabledModules ) )
}
@ -354,7 +359,7 @@ func (oAdmin *OvpnAdmin) downloadCertsHandler(w http.ResponseWriter, r *http.Req
http . Error ( w , ` { "status":"error"} ` , http . StatusLocked )
return
}
r . ParseForm ( )
_ = r . ParseForm ( )
token := r . Form . Get ( "token" )
if token != oAdmin . masterSyncToken {
@ -372,7 +377,7 @@ func (oAdmin *OvpnAdmin) downloadCcdHandler(w http.ResponseWriter, r *http.Reque
http . Error ( w , ` { "status":"error"} ` , http . StatusLocked )
return
}
r . ParseForm ( )
_ = r . ParseForm ( )
token := r . Form . Get ( "token" )
if token != oAdmin . masterSyncToken {
@ -385,10 +390,26 @@ func (oAdmin *OvpnAdmin) downloadCcdHandler(w http.ResponseWriter, r *http.Reque
http . ServeFile ( w , r , ccdArchivePath )
}
var app OpenVPNPKI
func main ( ) {
kingpin . Version ( version )
kingpin . Parse ( )
log . SetLevel ( logLevels [ * logLevel ] )
log . SetFormatter ( & log . JSONFormatter { } )
if * kubernetesBackend {
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
@ -430,14 +451,6 @@ func main() {
go ovpnAdmin . syncWithMaster ( )
}
if * debug {
log . Println ( "Runnnig in debug mode" )
}
if * verbose {
log . Println ( "Runnnig in verbose mode" )
}
ovpnAdmin . templates = packr . New ( "template" , "./templates" )
staticBox := packr . New ( "static" , "./frontend/static" )
@ -550,7 +563,7 @@ func (oAdmin *OvpnAdmin) getClientConfigTemplate() *template.Template {
} else {
clientConfigTpl , clientConfigTplErr := oAdmin . templates . FindString ( "client.conf.tpl" )
if clientConfigTplErr != nil {
log . Print ln( "ERROR: clientConfigTpl not found in templates box" )
log . Error ln( "clientConfigTpl not found in templates box" )
}
return template . Must ( template . New ( "client-config" ) . Parse ( clientConfigTpl ) )
}
@ -564,20 +577,43 @@ func (oAdmin *OvpnAdmin) renderClientConfig(username string) string {
parts := strings . SplitN ( server , ":" , 3 )
hosts = append ( hosts , OpenvpnServer { Host : parts [ 0 ] , Port : parts [ 1 ] , Protocol : parts [ 2 ] } )
}
if * openvpnServerBehindLB {
hosts = getOvpnServerHostsFromKubeApi ( )
var err error
hosts , err = getOvpnServerHostsFromKubeApi ( )
if err != nil {
log . Error ( err )
}
}
if * debug {
log . Printf ( "WARNING: hosts for %s\n %v" , username , hosts )
externalHost := os . Getenv ( "EXTERNAL_HOST" )
externalPort := os . Getenv ( "EXTERNAL_PORT" )
if externalHost != "" {
hosts [ 0 ] . Host = externalHost
}
if externalPort != "" {
hosts [ 0 ] . Port = externalPort
}
log . Debugf ( "hosts for %s\n %v" , username , hosts )
conf := openvpnClientConfig { }
conf . Hosts = hosts
conf . CA = fRead ( * easyrsaDirPath + "/pki/ca.crt" )
conf . Cert = fRead ( * easyrsaDirPath + "/pki/issued/" + username + ".crt" )
conf . Key = fRead ( * easyrsaDirPath + "/pki/private/" + username + ".key" )
conf . TLS = fRead ( * easyrsaDirPath + "/pki/ta.key" )
if * kubernetesBackend {
clientCert := app . easyrsaGetClientCert ( username )
conf . Cert = clientCert . CertPEM . String ( )
conf . Key = clientCert . PrivKeyPEM . String ( )
} else {
conf . Cert = fRead ( * easyrsaDirPath + "/pki/issued/" + username + ".crt" )
conf . Key = fRead ( * easyrsaDirPath + "/pki/private/" + username + ".key" )
}
conf . PasswdAuth = * authByPassword
t := oAdmin . getClientConfigTemplate ( )
@ -585,19 +621,17 @@ func (oAdmin *OvpnAdmin) renderClientConfig(username string) string {
var tmp bytes . Buffer
err := t . Execute ( & tmp , conf )
if err != nil {
log . Printf ( "ERROR: something goes wrong during rendering config for %s\n" , username )
if * debug {
log . Printf ( "DEBUG: rendering config for %s failed with error %v\n" , username , err )
}
log . Errorf ( "ERROR: something goes wrong during rendering config for %s\n" , username )
log . Debugf ( "DEBUG: rendering config for %s failed with error %v\n" , username , err )
}
hosts = nil
if * verbose {
log . Print f( "INFO: Rendered config for user %s: %+v\n" , username , tmp . String ( ) )
}
log . Debug f( "INFO: Rendered config for user %s: %+v\n" , username , tmp . String ( ) )
return fmt . Sprintf ( "%+v\n" , tmp . String ( ) )
}
log . Print f( "WARNING: User \"%s\" not found" , username )
log . Warn f( "WARNING: User \"%s\" not found" , username )
return fmt . Sprintf ( "User \"%s\" not found" , username )
}
@ -607,7 +641,7 @@ func (oAdmin *OvpnAdmin) getCcdTemplate() *template.Template {
} else {
ccdTpl , ccdTplErr := oAdmin . templates . FindString ( "ccd.tpl" )
if ccdTplErr != nil {
log . Print f( "ERROR: ccdTpl not found in templates box" )
log . Error f( "ccdTpl not found in templates box" )
}
return template . Must ( template . New ( "ccd" ) . Parse ( ccdTpl ) )
}
@ -650,7 +684,7 @@ func (oAdmin *OvpnAdmin) modifyCcd(ccd Ccd) (bool, string) {
var tmp bytes . Buffer
tplErr := t . Execute ( & tmp , ccd )
if tplErr != nil {
log . Print ln( tplErr )
log . Error ln( tplErr )
}
fWrite ( * ccdDir + "/" + ccd . User , tmp . String ( ) )
return true , "ccd updated successfully"
@ -662,56 +696,46 @@ func (oAdmin *OvpnAdmin) modifyCcd(ccd Ccd) (bool, string) {
func validateCcd ( ccd Ccd ) ( bool , string ) {
ccdErr := ""
if ccd . ClientAddress != "dynamic" {
_ , ovpnNet , err := net . ParseCIDR ( * openvpnNetwork )
if err != nil {
log . Println ( err )
}
if ! checkStaticAddressIsFree ( ccd . ClientAddress , ccd . User ) {
ccdErr = fmt . Sprintf ( "ClientAddress \"%s\" already assigned to another user" , ccd . ClientAddress )
if * debug {
log . Printf ( "ERROR: Modify ccd for user %s: %s\n" , ccd . User , ccdErr )
}
return false , ccdErr
}
if net . ParseIP ( ccd . ClientAddress ) == nil {
ccdErr = fmt . Sprintf ( "ClientAddress \"%s\" not a valid IP address" , ccd . ClientAddress )
if * debug {
log . Printf ( "ERROR: Modify ccd for user %s: %s\n" , 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 )
if * debug {
log . Printf ( "ERROR: Modify ccd for user %s: %s\n" , 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 )
if * debug {
log . Printf ( "ERROR: Modify ccd for user %s: %s\n" , 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 )
if * debug {
log . Printf ( "ERROR: Modify ccd for user %s: %s\n" , ccd . User , ccdErr )
}
return false , ccdErr
}
}
ccdErr := ""
if ccd . ClientAddress != "dynamic" {
_ , ovpnNet , err := net . ParseCIDR ( * openvpnNetwork )
if err != nil {
log . Errorln ( err )
}
if ! checkStaticAddressIsFree ( ccd . ClientAddress , ccd . User ) {
ccdErr = fmt . Sprintf ( "ClientAddress \"%s\" already assigned to another user" , ccd . ClientAddress )
log . Debugf ( "ERROR: Modify ccd for user %s: %s\n" , 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 ( "ERROR: Modify ccd for user %s: %s\n" , 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 ( "ERROR: Modify ccd for user %s: %s\n" , 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 ( "ERROR: Modify ccd for user %s: %s\n" , 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 ( "ERROR: Modify ccd for user %s: %s\n" , ccd . User , ccdErr )
return false , ccdErr
}
}
return true , ccdErr
}
@ -751,7 +775,18 @@ func validatePassword(password string) bool {
}
func checkUserExist ( username string ) bool {
for _ , u := range indexTxtParser ( fRead ( * indexTxtPath ) ) {
var indexTxt string
if * kubernetesBackend {
var err error
indexTxt , err = app . secretGetIndexTxt ( )
if err != nil {
log . Error ( err )
}
} else {
indexTxt = fRead ( * indexTxtPath )
}
for _ , u := range indexTxtParser ( indexTxt ) {
if u . DistinguishedName == ( "/CN=" + username ) {
return true
}
@ -769,7 +804,18 @@ func (oAdmin *OvpnAdmin) usersList() []OpenvpnClient {
connectedUsers := 0
apochNow := time . Now ( ) . Unix ( )
for _ , line := range indexTxtParser ( fRead ( * indexTxtPath ) ) {
var indexTxt string
if * kubernetesBackend {
var err error
indexTxt , err = app . secretGetIndexTxt ( )
if err != nil {
log . Error ( err )
}
} else {
indexTxt = fRead ( * indexTxtPath )
}
for _ , line := range indexTxtParser ( indexTxt ) {
if line . Identity != "server" {
totalCerts += 1
ovpnClient := OpenvpnClient { Identity : line . Identity , ExpirationDate : parseDateToString ( indexTxtDateLayout , line . ExpirationDate , stringDateFormat ) }
@ -808,7 +854,7 @@ func (oAdmin *OvpnAdmin) usersList() []OpenvpnClient {
otherCerts := totalCerts - validCerts - revokedCerts - expiredCerts
if otherCerts != 0 {
log . Print f( "WARNING: there are %d otherCerts\n" , otherCerts )
log . Warn f( "there are %d otherCerts\n" , otherCerts )
}
ovpnClientsTotal . Set ( float64 ( totalCerts ) )
@ -824,41 +870,41 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (bool, string) {
if checkUserExist ( username ) {
ucErr = fmt . Sprintf ( "User \"%s\" already exists\n" , username )
if * debug {
log . Printf ( "ERROR: userCreate: %s\n" , ucErr )
}
log . Debugf ( "ERROR: userCreate: %s\n" , ucErr )
return false , ucErr
}
if ! validateUsername ( username ) {
ucErr = fmt . Sprintf ( "Username \"%s\" incorrect, you can use only %s\n" , username , usernameRegexp )
if * debug {
log . Printf ( "ERROR: userCreate: %s\n" , ucErr )
}
log . Debugf ( "ERROR: userCreate: %s\n" , ucErr )
return false , ucErr
}
if * authByPassword {
if ! validatePassword ( password ) {
ucErr = fmt . Sprintf ( "Password too short, password length must be greater or equal %d" , passwordMinLength )
if * debug {
log . Printf ( "ERROR: userCreate: %s\n" , ucErr )
}
log . Debugf ( "ERROR: userCreate: %s\n" , ucErr )
return false , ucErr
}
}
o := runBash ( fmt . Sprintf ( "date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && easyrsa build-client-full %s nopass" , * easyrsaDirPath , username ) )
log . Println ( o )
if * kubernetesBackend {
err := app . easyrsaBuildClient ( username )
if err != nil {
log . Error ( err )
}
} else {
o := runBash ( fmt . Sprintf ( "date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && easyrsa build-client-full %s nopass" , * easyrsaDirPath , username ) )
log . Println ( o )
}
if * authByPassword {
o = runBash ( fmt . Sprintf ( "openvpn-user create --db.path %s --user %s --password %s" , * authDatabase , username , password ) )
o : = runBash ( fmt . Sprintf ( "openvpn-user create --db.path %s --user %s --password %s" , * authDatabase , username , password ) )
log . Println ( o )
}
if * verbose {
log . Printf ( "INFO: user created: %s\n" , username )
}
log . Infof ( "INFO: user created: %s\n" , username )
oAdmin . clients = oAdmin . usersList ( )
@ -873,9 +919,7 @@ func (oAdmin *OvpnAdmin) userChangePassword(username, password string) (bool, st
if ! validatePassword ( password ) {
ucpErr := fmt . Sprintf ( "Password for too short, password length must be greater or equal %d" , passwordMinLength )
if * debug {
log . Printf ( "ERROR: userChangePassword: %s\n" , ucpErr )
}
log . Debugf ( "ERROR: userChangePassword: %s\n" , ucpErr )
return false , ucpErr
}
@ -888,9 +932,8 @@ func (oAdmin *OvpnAdmin) userChangePassword(username, password string) (bool, st
o = runBash ( fmt . Sprintf ( "openvpn-user change-password --db.path %s --user %s --password %s" , * authDatabase , username , password ) )
log . Println ( o )
if * verbose {
log . Printf ( "INFO: password for user %s was changed\n" , username )
}
log . Infof ( "INFO: password for user %s was changed\n" , username )
return true , "Password changed"
}
@ -907,61 +950,78 @@ func (oAdmin *OvpnAdmin) getUserStatistic(username string) clientStatus {
}
func ( oAdmin * OvpnAdmin ) userRevoke ( username string ) string {
var shellOut string
if checkUserExist ( username ) {
// check certificate valid flag 'V'
o := runBash ( fmt . Sprintf ( "date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && echo yes | easyrsa revoke %s && easyrsa gen-crl" , * easyrsaDirPath , username ) )
if * kubernetesBackend {
err := app . easyrsaRevoke ( username )
if err != nil {
log . Error ( err )
}
} else {
shellOut = runBash ( fmt . Sprintf ( "date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && echo yes | easyrsa revoke %s && easyrsa gen-crl" , * easyrsaDirPath , username ) )
log . Debug ( shellOut )
}
if * authByPassword {
o = runBash ( fmt . Sprintf ( "openvpn-user revoke --db-path %s --user %s" , * authDatabase , username ) )
//fmt.Println(o)
shellOut = runBash ( fmt . Sprintf ( "openvpn-user revoke --db-path %s --user %s" , * authDatabase , username ) )
//log.Debug(shellOut )
}
crlFix ( )
userConnected , userConnectedTo := isUserConnected ( username , oAdmin . activeClients )
if userConnected {
oAdmin . mgmtKillUserConnection ( username , userConnectedTo )
log . Print f( "Session for user \"%s\" session killed\n" , username )
log . Info f( "Session for user \"%s\" session killed\n" , username )
}
oAdmin . clients = oAdmin . usersList ( )
return fmt . Sprintln ( o )
return fmt . Sprintln ( shellOut )
}
log . Print f( "User \"%s\" not found\n" , username )
log . Info f( "User \"%s\" not found\n" , username )
return fmt . Sprintf ( "User \"%s\" not found" , username )
}
func ( oAdmin * OvpnAdmin ) userUnrevoke ( username string ) string {
if checkUserExist ( username ) {
// 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 = ""
o := runBash ( fmt . Sprintf ( "cd %s && cp pki/revoked/certs_by_serial/%s.crt pki/issued/%s.crt" , * easyrsaDirPath , usersFromIndexTxt [ i ] . SerialNumber , username ) )
//fmt.Println(o)
o = runBash ( fmt . Sprintf ( "cd %s && cp pki/revoked/certs_by_serial/%s.crt pki/certs_by_serial/%s.pem" , * easyrsaDirPath , usersFromIndexTxt [ i ] . SerialNumber , usersFromIndexTxt [ i ] . SerialNumber ) )
//fmt.Println(o)
o = runBash ( fmt . Sprintf ( "cd %s && cp pki/revoked/private_by_serial/%s.key pki/private/%s.key" , * easyrsaDirPath , usersFromIndexTxt [ i ] . SerialNumber , username ) )
//fmt.Println(o)
o = runBash ( fmt . Sprintf ( "cd %s && cp pki/revoked/reqs_by_serial/%s.req pki/reqs/%s.req" , * easyrsaDirPath , usersFromIndexTxt [ i ] . SerialNumber , username ) )
//fmt.Println(o)
fWrite ( * indexTxtPath , renderIndexTxt ( usersFromIndexTxt ) )
//fmt.Print(renderIndexTxt(usersFromIndexTxt))
o = runBash ( fmt . Sprintf ( "cd %s && easyrsa gen-crl" , * easyrsaDirPath ) )
//fmt.Println(o)
if * authByPassword {
o = runBash ( fmt . Sprintf ( "openvpn-user restore --db-path %s --user %s" , * authDatabase , username ) )
if * kubernetesBackend {
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 = ""
o := runBash ( fmt . Sprintf ( "cd %s && cp pki/revoked/certs_by_serial/%s.crt pki/issued/%s.crt" , * easyrsaDirPath , usersFromIndexTxt [ i ] . SerialNumber , username ) )
//fmt.Println(o)
o = runBash ( fmt . Sprintf ( "cd %s && cp pki/revoked/certs_by_serial/%s.crt pki/certs_by_serial/%s.pem" , * easyrsaDirPath , usersFromIndexTxt [ i ] . SerialNumber , usersFromIndexTxt [ i ] . SerialNumber ) )
//fmt.Println(o)
o = runBash ( fmt . Sprintf ( "cd %s && cp pki/revoked/private_by_serial/%s.key pki/private/%s.key" , * easyrsaDirPath , usersFromIndexTxt [ i ] . SerialNumber , username ) )
//fmt.Println(o)
o = runBash ( fmt . Sprintf ( "cd %s && cp pki/revoked/reqs_by_serial/%s.req pki/reqs/%s.req" , * easyrsaDirPath , usersFromIndexTxt [ i ] . SerialNumber , username ) )
//fmt.Println(o)
fWrite ( * indexTxtPath , renderIndexTxt ( usersFromIndexTxt ) )
//fmt.Print(renderIndexTxt(usersFromIndexTxt))
o = runBash ( fmt . Sprintf ( "cd %s && easyrsa gen-crl" , * easyrsaDirPath ) )
//fmt.Println(o)
if * authByPassword {
o = runBash ( fmt . Sprintf ( "openvpn-user restore --db-path %s --user %s" , * authDatabase , username ) )
//fmt.Println(o)
}
crlFix ( )
o = ""
fmt . Println ( o )
break
}
crlFix ( )
o = ""
fmt . Println ( o )
break
}
}
fWrite ( * indexTxtPath , renderIndexTxt ( usersFromIndexTxt ) )
fmt . Print ( renderIndexTxt ( usersFromIndexTxt ) )
}
fWrite ( * indexTxtPath , renderIndexTxt ( usersFromIndexTxt ) )
fmt . Print ( renderIndexTxt ( usersFromIndexTxt ) )
crlFix ( )
oAdmin . clients = oAdmin . usersList ( )
return fmt . Sprintf ( "{\"msg\":\"User %s successfully unrevoked\"}" , username )
@ -1034,7 +1094,7 @@ func (oAdmin *OvpnAdmin) mgmtConnectedUsersParser(text, serverName string) []cli
func ( oAdmin * OvpnAdmin ) mgmtKillUserConnection ( username , serverName string ) {
conn , err := net . Dial ( "tcp" , oAdmin . mgmtInterfaces [ serverName ] )
if err != nil {
log . Print f( "WARNING: openvpn mgmt interface for %s is not reachable by addr %s\n" , serverName , oAdmin . mgmtInterfaces [ serverName ] )
log . Error f( "WARNING: openvpn mgmt interface for %s is not reachable by addr %s\n" , serverName , oAdmin . mgmtInterfaces [ serverName ] )
return
}
oAdmin . mgmtRead ( conn ) // read welcome message
@ -1049,7 +1109,7 @@ func (oAdmin *OvpnAdmin) mgmtGetActiveClients() []clientStatus {
for srv , addr := range oAdmin . mgmtInterfaces {
conn , err := net . Dial ( "tcp" , addr )
if err != nil {
log . Print f( "WARNING: openvpn mgmt interface for %s is not reachable by addr %s\n" , srv , addr )
log . Error f( "WARNING: openvpn mgmt interface for %s is not reachable by addr %s\n" , srv , addr )
break
}
oAdmin . mgmtRead ( conn ) // read welcome message
@ -1075,7 +1135,7 @@ func (oAdmin *OvpnAdmin) downloadCerts() bool {
}
err := fDownload ( certsArchivePath , * masterHost + downloadCertsApiUrl + "?token=" + oAdmin . masterSyncToken , oAdmin . masterHostBasicAuth )
if err != nil {
log . Print ln( err )
log . Error ln( err )
return false
}
@ -1089,7 +1149,7 @@ func (oAdmin *OvpnAdmin) downloadCcd() bool {
err := fDownload ( ccdArchivePath , * masterHost + downloadCcdApiUrl + "?token=" + oAdmin . masterSyncToken , oAdmin . masterHostBasicAuth )
if err != nil {
log . Print ln( err )
log . Error ln( err )
return false
}
@ -1127,25 +1187,25 @@ func (oAdmin *OvpnAdmin) syncDataFromMaster() {
for certsDownloadFailed && certsDownloadRetries < retryCountMax {
certsDownloadRetries += 1
log . Print f( "Downloading certs archive from master. Attempt %d\n" , certsDownloadRetries )
log . Info f( "Downloading certs archive from master. Attempt %d\n" , certsDownloadRetries )
if oAdmin . downloadCerts ( ) {
certsDownloadFailed = false
log . Print ln( "Decompression certs archive from master" )
log . Info ln( "Decompression certs archive from master" )
unArchiveCerts ( )
} else {
log . Print f( "WARNING: something goes wrong during downloading certs from master. Attempt %d\n" , certsDownloadRetries )
log . Warn f( "WARNING: something goes wrong during downloading certs from master. Attempt %d\n" , certsDownloadRetries )
}
}
for ccdDownloadFailed && ccdDownloadRetries < retryCountMax {
ccdDownloadRetries += 1
log . Print f( "Downloading ccd archive from master. Attempt %d\n" , ccdDownloadRetries )
log . Info f( "Downloading ccd archive from master. Attempt %d\n" , ccdDownloadRetries )
if oAdmin . downloadCcd ( ) {
ccdDownloadFailed = false
log . Print ln( "Decompression ccd archive from master" )
log . Info ln( "Decompression ccd archive from master" )
unArchiveCcd ( )
} else {
log . Print f( "WARNING: something goes wrong during downloading certs from master. Attempt %d\n" , ccdDownloadRetries )
log . Warn f( "WARNING: something goes wrong during downloading certs from master. Attempt %d\n" , ccdDownloadRetries )
}
}
@ -1162,31 +1222,29 @@ func (oAdmin *OvpnAdmin) syncWithMaster() {
}
}
func getOvpnServerHostsFromKubeApi ( ) [ ] OpenvpnServer {
func getOvpnServerHostsFromKubeApi ( ) ( [ ] OpenvpnServer , error ) {
var hosts [ ] OpenvpnServer
var lbHost string
config , err := rest . InClusterConfig ( )
if err != nil {
log . Print f( "ERROR: %s\n" , err . Error ( ) )
log . Error f( "ERROR: %s\n" , err . Error ( ) )
}
clientset , err := kubernetes . NewForConfig ( config )
if err != nil {
log . Print f( "ERROR: %s\n" , err . Error ( ) )
log . Error f( "ERROR: %s\n" , err . Error ( ) )
}
service , err := clientset . CoreV1 ( ) . Services ( fRead ( kubeNamespaceFilePath ) ) . Get ( context . TODO ( ) , * openvpnServiceName , metav1 . GetOptions { } )
if err != nil {
log . Printf ( "ERROR: %s\n" , err . Error ( ) )
log . Errorf ( "ERROR: %s\n" , err . Error ( ) )
return nil , err
}
if * debug {
log . Printf ( "Debug: service from kube api %v\n" , service )
log . Printf ( "Debug: service.Status from kube api %v\n" , service . Status )
log . Printf ( "Debug: service.Status.LoadBalancer from kube api %v\n" , service . Status . LoadBalancer )
}
log . Debugf ( "Debug: service from kube api %v\n" , service )
log . Debugf ( "Debug: service.Status from kube api %v\n" , service . Status )
log . Debugf ( "Debug: service.Status.LoadBalancer from kube api %v\n" , service . Status . LoadBalancer )
if service . Status . LoadBalancer . Ingress [ 0 ] . Hostname != "" {
lbHost = service . Status . LoadBalancer . Ingress [ 0 ] . Hostname
@ -1194,19 +1252,23 @@ func getOvpnServerHostsFromKubeApi() []OpenvpnServer {
if service . Status . LoadBalancer . Ingress [ 0 ] . IP != "" {
lbHost = service . Status . LoadBalancer . Ingress [ 0 ] . IP
}
hosts = append ( hosts , OpenvpnServer { lbHost , strconv . Itoa ( int ( service . Spec . Ports [ 0 ] . Port ) ) , strings . ToLower ( string ( service . Spec . Ports [ 0 ] . Protocol ) ) } )
hosts = append ( hosts , OpenvpnServer { lbHost , strconv . Itoa ( int ( service . Spec . Ports [ 0 ] . Port ) ) , strings . ToLower ( string ( service . Spec . Ports [ 0 ] . Protocol ) ) } )
return hosts
return hosts , nil
}
func getOvpnCaCertExpireDate ( ) time . Time {
if * kubernetesBackend {
return app . CACert . NotAfter
}
caCertPath := * easyrsaDirPath + "/pki/ca.crt"
caCertExpireDate := runBash ( fmt . Sprintf ( "openssl x509 -in %s -noout -enddate | awk -F \"=\" {'print $2'}" , caCertPath ) )
dateLayout := "Jan 2 15:04:05 2006 MST"
t , err := time . Parse ( dateLayout , strings . TrimSpace ( caCertExpireDate ) )
if err != nil {
log . Print f( "WARNING: can`t parse expire date for CA cert: %v\n" , err )
log . Error f( "WARNING: can`t parse expire date for CA cert: %v\n" , err )
return time . Now ( )
}
@ -1215,12 +1277,12 @@ func getOvpnCaCertExpireDate() time.Time {
// https://community.openvpn.net/openvpn/ticket/623
func crlFix ( ) {
err1 := os . Chmod ( * easyrsaDirPath + "/pki" , 0755 )
if err1 != nil {
log . Print ln( err1 )
err := os . Chmod ( * easyrsaDirPath + "/pki" , 0755 )
if err != nil {
log . Error ln( err )
}
err2 : = os . Chmod ( * easyrsaDirPath + "/pki/crl.pem" , 0644 )
if err2 != nil {
log . Print ln( err2 )
err = os . Chmod ( * easyrsaDirPath + "/pki/crl.pem" , 0644 )
if err != nil {
log . Error ln( err )
}
}