diff --git a/helpers.go b/helpers.go index 3a9f800..01cefe8 100644 --- a/helpers.go +++ b/helpers.go @@ -3,17 +3,18 @@ package main import ( "fmt" "io/ioutil" - "log" "net/http" "os" "os/exec" "time" + + log "github.com/sirupsen/logrus" ) func parseDate(layout,datetime string) time.Time { t, err := time.Parse(layout, datetime) if err != nil { - log.Println(err) + log.Errorln(err) } return t } @@ -27,9 +28,7 @@ func parseDateToUnix(layout,datetime string) int64 { } func runBash(script string) string { - if *debug { - log.Println(script) - } + log.Debugln(script) cmd := exec.Command("bash", "-c", script) stdout, err := cmd.CombinedOutput() if err != nil { @@ -65,7 +64,7 @@ func fCreate(path string) bool { if os.IsNotExist(err) { var file, err = os.Create(path) if err != nil { - log.Println(err) + log.Errorln(err) return false } defer file.Close() @@ -100,7 +99,7 @@ func fDownload(path, url string, basicAuth bool) error { } if resp.StatusCode != 200 { - log.Printf("WARNING: Download file operation for url %s finished with status code %d\n", url, resp.StatusCode ) + log.Warnf("WARNING: Download file operation for url %s finished with status code %d\n", url, resp.StatusCode ) } defer resp.Body.Close() diff --git a/main.go b/main.go index 327b14b..dff3a76 100644 --- a/main.go +++ b/main.go @@ -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.Println(err) + log.Errorln(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.Println("ERROR: clientConfigTpl not found in templates box") + log.Errorln("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.Printf("INFO: Rendered config for user %s: %+v\n", username, tmp.String()) - } + + log.Debugf("INFO: Rendered config for user %s: %+v\n", username, tmp.String()) + return fmt.Sprintf("%+v\n", tmp.String()) } - log.Printf("WARNING: User \"%s\" not found", username) + log.Warnf("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.Printf("ERROR: ccdTpl not found in templates box") + log.Errorf("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.Println(tplErr) + log.Errorln(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 := "" + ccdErr := "" - if ccd.ClientAddress != "dynamic" { - _, ovpnNet, err := net.ParseCIDR(*openvpnNetwork) - if err != nil { - log.Println(err) - } + 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) - if *debug { - log.Printf("ERROR: Modify ccd for user %s: %s\n", ccd.User, ccdErr) - } - return false, ccdErr - } + 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) - 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) + 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) - 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) + 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) - 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) + 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) - 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) + 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.Printf("WARNING: there are %d otherCerts\n", otherCerts) + log.Warnf("there are %d otherCerts\n", otherCerts) } ovpnClientsTotal.Set(float64(totalCerts)) @@ -824,42 +870,42 @@ 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 *authByPassword { - o = runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password)) + 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 *verbose { - log.Printf("INFO: user created: %s\n", username) + + if *authByPassword { + o := runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password)) + log.Println(o) } + log.Infof("INFO: user created: %s\n", username) + oAdmin.clients = oAdmin.usersList() return true, ucErr @@ -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.Printf("Session for user \"%s\" session killed\n", username) + log.Infof("Session for user \"%s\" session killed\n", username) } oAdmin.clients = oAdmin.usersList() - return fmt.Sprintln(o) + return fmt.Sprintln(shellOut) } - log.Printf("User \"%s\" not found\n", username) + log.Infof("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.Printf("WARNING: openvpn mgmt interface for %s is not reachable by addr %s\n", serverName, oAdmin.mgmtInterfaces[serverName]) + log.Errorf("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.Printf("WARNING: openvpn mgmt interface for %s is not reachable by addr %s\n", srv, addr) + log.Errorf("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.Println(err) + log.Errorln(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.Println(err) + log.Errorln(err) return false } @@ -1127,25 +1187,25 @@ func (oAdmin *OvpnAdmin) syncDataFromMaster() { for certsDownloadFailed && certsDownloadRetries < retryCountMax { certsDownloadRetries += 1 - log.Printf("Downloading certs archive from master. Attempt %d\n", certsDownloadRetries) + log.Infof("Downloading certs archive from master. Attempt %d\n", certsDownloadRetries) if oAdmin.downloadCerts() { certsDownloadFailed = false - log.Println("Decompression certs archive from master") + log.Infoln("Decompression certs archive from master") unArchiveCerts() } else { - log.Printf("WARNING: something goes wrong during downloading certs from master. Attempt %d\n", certsDownloadRetries) + log.Warnf("WARNING: something goes wrong during downloading certs from master. Attempt %d\n", certsDownloadRetries) } } for ccdDownloadFailed && ccdDownloadRetries < retryCountMax { ccdDownloadRetries += 1 - log.Printf("Downloading ccd archive from master. Attempt %d\n", ccdDownloadRetries) + log.Infof("Downloading ccd archive from master. Attempt %d\n", ccdDownloadRetries) if oAdmin.downloadCcd() { ccdDownloadFailed = false - log.Println("Decompression ccd archive from master") + log.Infoln("Decompression ccd archive from master") unArchiveCcd() } else { - log.Printf("WARNING: something goes wrong during downloading certs from master. Attempt %d\n", ccdDownloadRetries) + log.Warnf("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.Printf("ERROR: %s\n", err.Error()) + log.Errorf("ERROR: %s\n", err.Error()) } clientset, err := kubernetes.NewForConfig(config) if err != nil { - log.Printf("ERROR: %s\n", err.Error()) + log.Errorf("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.Printf("WARNING: can`t parse expire date for CA cert: %v\n", err) + log.Errorf("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.Println(err1) + err := os.Chmod(*easyrsaDirPath+"/pki", 0755) + if err != nil { + log.Errorln(err) } - err2 := os.Chmod(*easyrsaDirPath+"/pki/crl.pem", 0644) - if err2 != nil { - log.Println(err2) + err = os.Chmod(*easyrsaDirPath+"/pki/crl.pem", 0644) + if err != nil { + log.Errorln(err) } }