diff --git a/main.go b/main.go
index 0473114..e012991 100644
--- a/main.go
+++ b/main.go
@@ -20,7 +20,9 @@ import (
)
const (
- usernameRegexp = `^([a-zA-Z0-9_.-])+$`
+ usernameRegexp = `^([a-zA-Z0-9_.-@])+$`
+ passwordRegexp = `^([a-zA-Z0-9_.-@])+$`
+ passwordMinLength = 6
downloadCertsApiUrl = "/api/data/certs/download"
downloadCcdApiUrl = "/api/data/ccd/download"
certsArchiveFileName = "certs.tar.gz"
@@ -42,14 +44,17 @@ var (
masterSyncToken = kingpin.Flag("master.sync-token", "master host data sync security token").Default("justasimpleword").PlaceHolder("TOKEN").String()
openvpnServer = kingpin.Flag("ovpn.host","host(s) for openvpn server").Default("127.0.0.1:7777").PlaceHolder("HOST:PORT").Strings()
openvpnNetwork = kingpin.Flag("ovpn.network","network for openvpn server").Default("172.16.100.0/24").String()
- mgmtListenHost = kingpin.Flag("mgmt.host","host for openvpn server mgmt interface").Default("127.0.0.1").String()
- mgmtListenPort = kingpin.Flag("mgmt.port","port for openvpn server mgmt interface").Default("8989").String()
+ mgmtAddress = kingpin.Flag("mgmt","comma separated (alias=addresses) for openvpn servers mgmt interfaces").Default("main=127.0.0.1:8989").Strings()
+ //mgmtListenPort = kingpin.Flag("mgmt.port","port for openvpn server mgmt interface").Default("8989").String()
metricsPath = kingpin.Flag("metrics.path", "URL path for surfacing collected metrics").Default("/metrics").String()
easyrsaDirPath = kingpin.Flag("easyrsa.path", "path to easyrsa dir").Default("/mnt/easyrsa").String()
indexTxtPath = kingpin.Flag("easyrsa.index-path", "path to easyrsa index file.").Default("/mnt/easyrsa/pki/index.txt").String()
ccdDir = kingpin.Flag("ccd.path", "path to client-config-dir").Default("/mnt/ccd").String()
+ authByPassword = kingpin.Flag("auth.password", "Enable additional password authorization.").Default("false").Bool()
+ authDatabase = kingpin.Flag("auth.db", "Database path fort password authorization.").Default("/mnt/easyrsa/pki/users.db").String()
staticPath = kingpin.Flag("static.path", "path to static dir").Default("./static").String()
debug = kingpin.Flag("debug", "Enable debug mode.").Default("false").Bool()
+ verbose = kingpin.Flag("verbose", "Enable verbose mode.").Default("false").Bool()
certsArchivePath = "/tmp/" + certsArchiveFileName
ccdArchivePath = "/tmp/" + ccdArchiveFileName
@@ -103,7 +108,7 @@ var (
ovpnClientConnectionInfo = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "ovpn_client_connection_info",
- Help: "openvpn user connection info. ip - assigned address from opvn network. value - last time when connection was refreshed in unix format",
+ Help: "openvpn user connection info. ip - assigned address from ovpn network. value - last time when connection was refreshed in unix format",
},
[]string{"client", "ip"},
)
@@ -140,8 +145,10 @@ type OpenvpnAdmin struct {
clients []OpenvpnClient
activeClients []clientStatus
promRegistry *prometheus.Registry
+ mgmtInterfaces map[string]string
}
+
type OpenvpnServer struct {
Host string
Port string
@@ -153,6 +160,7 @@ type openvpnClientConfig struct {
Cert string
Key string
TLS string
+ PasswdAuth bool
}
type OpenvpnClient struct {
@@ -161,6 +169,7 @@ type OpenvpnClient struct {
ExpirationDate string `json:"ExpirationDate"`
RevocationDate string `json:"RevocationDate"`
ConnectionStatus string `json:"ConnectionStatus"`
+ ConnectionServer string `json:"ConnectionServer"`
}
type ccdRoute struct {
@@ -186,15 +195,16 @@ type indexTxtLine struct {
}
type clientStatus struct {
- CommonName string
- RealAddress string
- BytesReceived string
- BytesSent string
- ConnectedSince string
- VirtualAddress string
- LastRef string
+ CommonName string
+ RealAddress string
+ BytesReceived string
+ BytesSent string
+ ConnectedSince string
+ VirtualAddress string
+ LastRef string
ConnectedSinceFormatted string
LastRefFormatted string
+ ConnectedTo string
}
func (oAdmin *OpenvpnAdmin) userListHandler(w http.ResponseWriter, r *http.Request) {
@@ -214,7 +224,7 @@ func (oAdmin *OpenvpnAdmin) userCreateHandler(w http.ResponseWriter, r *http.Req
return
}
r.ParseForm()
- userCreated, userCreateStatus := oAdmin.userCreate(r.FormValue("username"))
+ userCreated, userCreateStatus := oAdmin.userCreate(r.FormValue("username"), r.FormValue("password"))
if userCreated {
w.WriteHeader(http.StatusOK)
@@ -244,6 +254,25 @@ func (oAdmin *OpenvpnAdmin) userUnrevokeHandler(w http.ResponseWriter, r *http.R
fmt.Fprintf(w, "%s", oAdmin.userUnrevoke(r.FormValue("username")))
}
+func (oAdmin *OpenvpnAdmin) userChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ if *authByPassword {
+ passwordChanged, passwordChangeMessage := oAdmin.userChangePassword(r.FormValue("username"), r.FormValue("password"))
+ if passwordChanged {
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `{"status":"ok", "message": "%s"}`, passwordChangeMessage)
+ return
+ } else {
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprintf(w, `{"status":"error", "message": "%s"}`, passwordChangeMessage)
+ return
+ }
+ } else {
+ http.Error(w, `{"status":"error"}`, http.StatusNotImplemented )
+ }
+
+}
+
func (oAdmin *OpenvpnAdmin) userShowConfigHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintf(w, "%s", oAdmin.renderClientConfig(r.FormValue("username")))
@@ -318,7 +347,7 @@ func (oAdmin *OpenvpnAdmin) downloadCertsHandler(w http.ResponseWriter, r *http.
http.ServeFile(w,r, certsArchivePath)
}
-func (oAdmin *OpenvpnAdmin) downloadCddHandler(w http.ResponseWriter, r *http.Request) {
+func (oAdmin *OpenvpnAdmin) downloadCcdHandler(w http.ResponseWriter, r *http.Request) {
if oAdmin.role == "slave" {
http.Error(w, `{"status":"error"}`, http.StatusLocked)
return
@@ -346,6 +375,13 @@ func main() {
ovpnAdmin.masterSyncToken = *masterSyncToken
ovpnAdmin.promRegistry = prometheus.NewRegistry()
+ ovpnAdmin.mgmtInterfaces = make(map[string]string)
+
+ for _, mgmtInterface := range *mgmtAddress {
+ parts := strings.SplitN(mgmtInterface, "=",2)
+ ovpnAdmin.mgmtInterfaces[parts[0]] = parts[len(parts)-1]
+ }
+
ovpnAdmin.registerMetrics()
ovpnAdmin.setState()
@@ -368,6 +404,7 @@ func main() {
http.HandleFunc("/api/server/role", ovpnAdmin.serverRoleHandler)
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/revoke", ovpnAdmin.userRevokeHandler)
http.HandleFunc("/api/user/unrevoke", ovpnAdmin.userUnrevokeHandler)
http.HandleFunc("/api/user/config/show", ovpnAdmin.userShowConfigHandler)
@@ -379,7 +416,7 @@ func main() {
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.downloadCddHandler)
+ http.HandleFunc(downloadCcdApiUrl, ovpnAdmin.downloadCcdHandler)
http.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{}))
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
@@ -415,7 +452,7 @@ func (oAdmin *OpenvpnAdmin) setState() {
oAdmin.activeClients = oAdmin.mgmtGetActiveClients()
oAdmin.clients = oAdmin.usersList()
- ovpnServerCaCertExpire.Set(float64((getOpvnCaCertExpireDate().Unix() - time.Now().Unix()) / 3600 / 24))
+ ovpnServerCaCertExpire.Set(float64((getOvpnCaCertExpireDate().Unix() - time.Now().Unix()) / 3600 / 24))
}
func (oAdmin *OpenvpnAdmin) updateState() {
@@ -479,6 +516,7 @@ func (oAdmin *OpenvpnAdmin) renderClientConfig(username string) string {
conf.Cert = fRead(*easyrsaDirPath + "/pki/issued/" + username + ".crt")
conf.Key = fRead(*easyrsaDirPath + "/pki/private/" + username + ".key")
conf.TLS = fRead(*easyrsaDirPath + "/pki/ta.key")
+ conf.PasswdAuth = *authByPassword
t, _ := template.ParseFiles("client.conf.tpl")
var tmp bytes.Buffer
@@ -624,6 +662,14 @@ func validateUsername(username string) bool {
return validUsername.MatchString(username)
}
+func validatePassword(password string) bool {
+ if len(password) < passwordMinLength {
+ return false
+ } else {
+ return true
+ }
+}
+
func checkUserExist(username string) bool {
for _, u := range indexTxtParser(fRead(*indexTxtPath)) {
if u.DistinguishedName == ("/CN=" + username) {
@@ -663,8 +709,12 @@ func (oAdmin *OpenvpnAdmin) usersList() []OpenvpnClient {
expiredCerts += 1
}
- if isUserConnected(line.Identity, oAdmin.activeClients) {
+ ovpnClient.ConnectionServer = ""
+
+ userConnected, userConnectedTo := isUserConnected(line.Identity, oAdmin.activeClients)
+ if userConnected {
ovpnClient.ConnectionStatus = "Connected"
+ ovpnClient.ConnectionServer = userConnectedTo
connectedUsers += 1
}
@@ -689,32 +739,82 @@ func (oAdmin *OpenvpnAdmin) usersList() []OpenvpnClient {
return users
}
-func (oAdmin *OpenvpnAdmin) userCreate(username string) (bool, string) {
+func (oAdmin *OpenvpnAdmin) userCreate(username, password string) (bool, string) {
ucErr := fmt.Sprintf("User \"%s\" created", username)
- // TODO: add password for user cert . priority=low
- if validateUsername(username) == false {
- ucErr = fmt.Sprintf("Username \"%s\" incorrect, you can use only %s\n", username, usernameRegexp)
- if *debug {
- log.Printf("ERROR: userCreate: %s", ucErr)
- }
- return false, ucErr
- }
+
if checkUserExist(username) {
ucErr = fmt.Sprintf("User \"%s\" already exists\n", username)
- if *debug {
- log.Printf("ERROR: userCreate: %s", ucErr)
- }
+ if *debug {
+ log.Printf("ERROR: userCreate: %s", 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", ucErr)
+ }
+ return false, ucErr
+ }
+
+ 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)
+ }
+ return false, ucErr
+ }
+
o := runBash(fmt.Sprintf("date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && easyrsa build-client-full %s nopass", *easyrsaDirPath, username))
fmt.Println(o)
- if *debug {
+
+ if *authByPassword {
+ o = runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password))
+ fmt.Println(o)
+ }
+
+ if *verbose {
log.Printf("INFO: user created: %s", username)
}
+
oAdmin.clients = oAdmin.usersList()
+
return true, ucErr
}
+func (oAdmin *OpenvpnAdmin) userChangePassword(username, password string) (bool, string) {
+
+ if checkUserExist(username) {
+ o := runBash(fmt.Sprintf("openvpn-user check --db.path %s --user %s | grep %s | wc -l", *authDatabase, username, username))
+ fmt.Println(o)
+
+ 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)
+ }
+ return false, ucpErr
+ }
+
+ if strings.TrimSpace(o) == "0" {
+ fmt.Println("Creating user in users.db")
+ o = runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password))
+ fmt.Println(o)
+ }
+
+ o = runBash(fmt.Sprintf("openvpn-user change-password --db.path %s --user %s --password %s", *authDatabase, username, password))
+ fmt.Println(o)
+
+ if *verbose {
+ log.Printf("INFO: password for user %s was changed", username)
+ }
+ return true, "Password changed"
+ }
+
+ return false, "User does not exist"
+}
+
func (oAdmin *OpenvpnAdmin) getUserStatistic(username string) clientStatus {
for _, u := range oAdmin.activeClients {
if u.CommonName == username {
@@ -728,6 +828,10 @@ func (oAdmin *OpenvpnAdmin) userRevoke(username string) 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 *authByPassword {
+ o = runBash(fmt.Sprintf("openvpn-user revoke --db-path %s --user %s", *authDatabase, username))
+ //fmt.Println(o)
+ }
crlFix()
oAdmin.clients = oAdmin.usersList()
return fmt.Sprintln(o)
@@ -757,6 +861,10 @@ func (oAdmin *OpenvpnAdmin) userUnrevoke(username string) string {
//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)
@@ -773,11 +881,6 @@ func (oAdmin *OpenvpnAdmin) userUnrevoke(username string) string {
return fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username)
}
-// TODO: add ability to change password for user cert . priority=low
-func userChangePassword(username string, newPassword string) bool {
-
- return false
-}
func (oAdmin *OpenvpnAdmin) mgmtRead(conn net.Conn) string {
buf := make([]byte, 32768)
@@ -786,7 +889,7 @@ func (oAdmin *OpenvpnAdmin) mgmtRead(conn net.Conn) string {
return s
}
-func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text string) []clientStatus {
+func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text, serverName string) []clientStatus {
var u []clientStatus
isClientList := false
isRouteTable := false
@@ -814,14 +917,14 @@ func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text string) []clientStatus
userName := user[0]
userAddress := user[1]
- userBytesRecieved:= user[2]
+ userBytesReceived:= user[2]
userBytesSent:= user[3]
userConnectedSince := user[4]
- userStatus := clientStatus{CommonName: userName, RealAddress: userAddress, BytesReceived: userBytesRecieved, BytesSent: userBytesSent, ConnectedSince: userConnectedSince}
+ userStatus := clientStatus{CommonName: userName, RealAddress: userAddress, BytesReceived: userBytesReceived, BytesSent: userBytesSent, ConnectedSince: userConnectedSince, ConnectedTo: serverName}
u = append(u, userStatus)
bytesSent, _ := strconv.Atoi(userBytesSent)
- bytesReceive, _ := strconv.Atoi(userBytesRecieved)
+ bytesReceive, _ := strconv.Atoi(userBytesReceived)
ovpnClientConnectionFrom.WithLabelValues(userName, userAddress).Set(float64(parseDateToUnix(ovpnStatusDateLayout, userConnectedSince)))
ovpnClientBytesSent.WithLabelValues(userName).Set(float64(bytesSent))
ovpnClientBytesReceived.WithLabelValues(userName).Set(float64(bytesReceive))
@@ -841,8 +944,8 @@ func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text string) []clientStatus
return u
}
-func (oAdmin *OpenvpnAdmin) mgmtKillUserConnection(username string) {
- conn, err := net.Dial("tcp", *mgmtListenHost+":"+*mgmtListenPort)
+func (oAdmin *OpenvpnAdmin) mgmtKillUserConnection(username, serverName string) {
+ conn, err := net.Dial("tcp", oAdmin.mgmtInterfaces[serverName])
if err != nil {
log.Println("ERROR: openvpn mgmt interface is not reachable")
return
@@ -854,25 +957,29 @@ func (oAdmin *OpenvpnAdmin) mgmtKillUserConnection(username string) {
}
func (oAdmin *OpenvpnAdmin) mgmtGetActiveClients() []clientStatus {
- conn, err := net.Dial("tcp", *mgmtListenHost+":"+*mgmtListenPort)
- if err != nil {
- log.Println("ERROR: openvpn mgmt interface is not reachable")
- return []clientStatus{}
+ var activeClients []clientStatus
+
+ for srv, addr := range oAdmin.mgmtInterfaces {
+ conn, err := net.Dial("tcp", addr)
+ if err != nil {
+ log.Printf("ERROR: openvpn mgmt interface for %s is not reachable by addr %s\n", srv, addr)
+ //return []clientStatus{}
+ }
+ oAdmin.mgmtRead(conn) // read welcome message
+ conn.Write([]byte("status\n"))
+ activeClients = append(activeClients, oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn), srv)...)
+ conn.Close()
}
- oAdmin.mgmtRead(conn) // read welcome message
- conn.Write([]byte("status\n"))
- activeClients := oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn))
- conn.Close()
return activeClients
}
-func isUserConnected(username string, connectedUsers []clientStatus) bool {
+func isUserConnected(username string, connectedUsers []clientStatus) (bool, string) {
for _, connectedUser := range connectedUsers {
if connectedUser.CommonName == username {
- return true
+ return true, connectedUser.ConnectedTo
}
}
- return false
+ return false, ""
}
func (oAdmin *OpenvpnAdmin) downloadCerts() bool {
@@ -968,7 +1075,7 @@ func (oAdmin *OpenvpnAdmin) syncWithMaster() {
}
}
-func getOpvnCaCertExpireDate() time.Time {
+func getOvpnCaCertExpireDate() time.Time {
caCertPath := *easyrsaDirPath + "/pki/ca.crt"
caCertExpireDate := runBash(fmt.Sprintf("openssl x509 -in %s -noout -enddate | awk -F \"=\" {'print $2'}", caCertPath))