From bb257692af7c8bf993919ad919b3139f9593cc49 Mon Sep 17 00:00:00 2001 From: Ilya Sosnovsky Date: Fri, 27 Nov 2020 10:23:59 +0300 Subject: [PATCH] Code refactoring to singleton; Added metrics --- go.mod | 5 +- go.sum | 11 +- helpers.go | 13 +- main.go | 448 ++++++++++++++++++++++++++++++++++++++++------------- werf.yaml | 2 +- 5 files changed, 360 insertions(+), 119 deletions(-) diff --git a/go.mod b/go.mod index e88f5a0..aad462b 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,8 @@ -module openvpn-web-ui +module openvpn-admin go 1.14 require ( - github.com/Electronn/openvpn_exporter v0.0.0-20181005212047-37f639dc9c7d - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/prometheus/client_golang v1.8.0 github.com/prometheus/common v0.15.0 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 diff --git a/go.sum b/go.sum index 0774fd6..748532e 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Electronn/openvpn_exporter v0.0.0-20181005212047-37f639dc9c7d h1:OsbfZPacTMb2ljULx/g4Xt9mSbkVx/lfuYQQwbgkD5c= -github.com/Electronn/openvpn_exporter v0.0.0-20181005212047-37f639dc9c7d/go.mod h1:wcYOspX0l7/kR2Pd59EUppVpfzvVu/I+vBgMmDOmNH0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -44,6 +42,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -92,6 +91,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -143,8 +143,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= @@ -201,6 +203,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -256,6 +259,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -353,6 +357,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -383,6 +388,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -396,6 +402,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/helpers.go b/helpers.go index ea6403a..4ebf171 100644 --- a/helpers.go +++ b/helpers.go @@ -10,13 +10,20 @@ import ( "time" ) -func indexTxtDateToHumanReadable(datetime string) string { - layout := "060102150405Z" +func parseDate(layout,datetime string) time.Time { t, err := time.Parse(layout, datetime) if err != nil { log.Println(err) } - return t.Format("2006-01-02 15:04:05") + return t +} + +func parseDateToString(layout,datetime,format string) string { + return parseDate(layout, datetime).Format(format) +} + +func parseDateToUnix(layout,datetime string) int64 { + return parseDate(layout, datetime).Unix() } func runBash(script string) string { diff --git a/main.go b/main.go index 83b4279..f18bceb 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,15 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" "gopkg.in/alecthomas/kingpin.v2" "log" "net" "net/http" "os" "regexp" + "strconv" "strings" "text/template" "time" @@ -22,9 +25,13 @@ const ( downloadCcdApiUrl = "/api/data/ccd/download" certsArchiveFileName = "certs.tar.gz" ccdArchiveFileName = "ccd.tar.gz" + indexTxtDateLayout = "060102150405Z" + stringDateFormat = "2006-01-02 15:04:05" + ovpnStatusDateLayout = "Mon Jan 2 15:04:05 2006" ) var ( + listenHost = kingpin.Flag("listen.host","host for openvpn-admin").Default("0.0.0.0").String() listenPort = kingpin.Flag("listen.port","port for openvpn-admin").Default("8080").String() serverRole = kingpin.Flag("role","server role master or slave").Default("master").HintOptions("master", "slave").String() @@ -37,6 +44,7 @@ var ( 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() + 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() @@ -45,11 +53,95 @@ var ( certsArchivePath = "/tmp/" + certsArchiveFileName ccdArchivePath = "/tmp/" + ccdArchiveFileName - lastSyncTime = "" - lastSuccessfulSyncTime = "" - masterHostBasicAuth = false + ) +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: "connected openvpn users", + }, + ) + + 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 opvn 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 OpenvpnAdmin struct { + role string + lastSyncTime string + lastSuccessfulSyncTime string + masterHostBasicAuth bool + masterSyncToken string + clients []OpenvpnClient + activeClients []clientStatus + promRegistry *prometheus.Registry +} + type OpenvpnServer struct { Host string Port string @@ -101,23 +193,28 @@ type clientStatus struct { ConnectedSince string VirtualAddress string LastRef string - ConnectedSinceFormated string - LastRefFormated string + ConnectedSinceFormatted string + LastRefFormatted string } - -func userListHandler(w http.ResponseWriter, r *http.Request) { - usersList, _ := json.Marshal(usersList()) +func (oAdmin *OpenvpnAdmin) userListHandler(w http.ResponseWriter, r *http.Request) { + usersList, _ := json.Marshal(oAdmin.clients) fmt.Fprintf(w, "%s", usersList) } -func userCreateHandler(w http.ResponseWriter, r *http.Request) { - if *serverRole == "slave" { +func (oAdmin *OpenvpnAdmin) userStatisticHandler(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + userStatistic, _ := json.Marshal(oAdmin.getUserStatistic(r.FormValue("username"))) + fmt.Fprintf(w, "%s", userStatistic) +} + +func (oAdmin *OpenvpnAdmin) userCreateHandler(w http.ResponseWriter, r *http.Request) { + if oAdmin.role == "slave" { http.Error(w, `{"status":"error"}`, http.StatusLocked) return } r.ParseForm() - userCreated, userCreateStatus := userCreate(r.FormValue("username")) + userCreated, userCreateStatus := oAdmin.userCreate(r.FormValue("username")) if userCreated { w.WriteHeader(http.StatusOK) @@ -128,44 +225,44 @@ func userCreateHandler(w http.ResponseWriter, r *http.Request) { } } -func userRevokeHandler(w http.ResponseWriter, r *http.Request) { - if *serverRole == "slave" { +func (oAdmin *OpenvpnAdmin) userRevokeHandler(w http.ResponseWriter, r *http.Request) { + if oAdmin.role == "slave" { http.Error(w, `{"status":"error"}`, http.StatusLocked) return } r.ParseForm() - fmt.Fprintf(w, "%s", userRevoke(r.FormValue("username"))) + fmt.Fprintf(w, "%s", oAdmin.userRevoke(r.FormValue("username"))) } -func userUnrevokeHandler(w http.ResponseWriter, r *http.Request) { - if *serverRole == "slave" { +func (oAdmin *OpenvpnAdmin) userUnrevokeHandler(w http.ResponseWriter, r *http.Request) { + if oAdmin.role == "slave" { http.Error(w, `{"status":"error"}`, http.StatusLocked) return } r.ParseForm() - fmt.Fprintf(w, "%s", userUnrevoke(r.FormValue("username"))) + fmt.Fprintf(w, "%s", oAdmin.userUnrevoke(r.FormValue("username"))) } -func userShowConfigHandler(w http.ResponseWriter, r *http.Request) { +func (oAdmin *OpenvpnAdmin) userShowConfigHandler(w http.ResponseWriter, r *http.Request) { r.ParseForm() - fmt.Fprintf(w, "%s", renderClientConfig(r.FormValue("username"))) + fmt.Fprintf(w, "%s", oAdmin.renderClientConfig(r.FormValue("username"))) } -func userDisconnectHandler(w http.ResponseWriter, r *http.Request) { +func (oAdmin *OpenvpnAdmin) userDisconnectHandler(w http.ResponseWriter, r *http.Request) { r.ParseForm() // fmt.Fprintf(w, "%s", userDisconnect(r.FormValue("username"))) fmt.Fprintf(w, "%s", r.FormValue("username")) } -func userShowCcdHandler(w http.ResponseWriter, r *http.Request) { +func (oAdmin *OpenvpnAdmin) userShowCcdHandler(w http.ResponseWriter, r *http.Request) { r.ParseForm() - ccd, _ := json.Marshal(getCcd(r.FormValue("username"))) + ccd, _ := json.Marshal(oAdmin.getCcd(r.FormValue("username"))) fmt.Fprintf(w, "%s", ccd) } -func userApplyCcdHandler(w http.ResponseWriter, r *http.Request) { - if *serverRole == "slave" { +func (oAdmin *OpenvpnAdmin) userApplyCcdHandler(w http.ResponseWriter, r *http.Request) { + if oAdmin.role == "slave" { http.Error(w, `{"status":"error"}`, http.StatusLocked) return } @@ -180,7 +277,7 @@ func userApplyCcdHandler(w http.ResponseWriter, r *http.Request) { log.Println(err) } - ccdApplied, applyStatus := modifyCcd(ccd) + ccdApplied, applyStatus := oAdmin.modifyCcd(ccd) if ccdApplied { w.WriteHeader(http.StatusOK) @@ -191,27 +288,27 @@ func userApplyCcdHandler(w http.ResponseWriter, r *http.Request) { } } -func serverRoleHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `{"status":"ok", "serverRole": "%s" }`, *serverRole) +func (oAdmin *OpenvpnAdmin) serverRoleHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{"status":"ok", "serverRole": "%s" }`, oAdmin.role) } -func lastSyncTimeHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, lastSyncTime) +func (oAdmin *OpenvpnAdmin) lastSyncTimeHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, oAdmin.lastSyncTime) } -func lastSuccessfulSyncTimeHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, lastSuccessfulSyncTime) +func (oAdmin *OpenvpnAdmin) lastSuccessfulSyncTimeHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, oAdmin.lastSuccessfulSyncTime) } -func downloadCertsHandler(w http.ResponseWriter, r *http.Request) { - if *serverRole == "slave" { +func (oAdmin *OpenvpnAdmin) downloadCertsHandler(w http.ResponseWriter, r *http.Request) { + if oAdmin.role == "slave" { http.Error(w, `{"status":"error"}`, http.StatusLocked) return } r.ParseForm() token := r.Form.Get("token") - if token != *masterSyncToken { + if token != oAdmin.masterSyncToken { http.Error(w, `{"status":"error"}`, http.StatusForbidden) return } @@ -221,15 +318,15 @@ func downloadCertsHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w,r, certsArchivePath) } -func downloadCddHandler(w http.ResponseWriter, r *http.Request) { - if *serverRole == "slave" { +func (oAdmin *OpenvpnAdmin) downloadCddHandler(w http.ResponseWriter, r *http.Request) { + if oAdmin.role == "slave" { http.Error(w, `{"status":"error"}`, http.StatusLocked) return } r.ParseForm() token := r.Form.Get("token") - if token != *masterSyncToken { + if token != oAdmin.masterSyncToken { http.Error(w, `{"status":"error"}`, http.StatusForbidden) return } @@ -242,35 +339,54 @@ func downloadCddHandler(w http.ResponseWriter, r *http.Request) { func main() { kingpin.Parse() + ovpnAdmin := new(OpenvpnAdmin) + ovpnAdmin.lastSyncTime = "unknown" + ovpnAdmin.role = *serverRole + ovpnAdmin.lastSuccessfulSyncTime = "unknown" + ovpnAdmin.masterSyncToken = *masterSyncToken + ovpnAdmin.promRegistry = prometheus.NewRegistry() + + ovpnAdmin.registerMetrics() + ovpnAdmin.setState() + + go ovpnAdmin.updateState() + if *masterBasicAuthPassword != "" && *masterBasicAuthUser != "" { - masterHostBasicAuth = true + ovpnAdmin.masterHostBasicAuth = true + } else { + ovpnAdmin.masterHostBasicAuth = false } - fmt.Println("Bind: http://" + *listenHost + ":" + *listenPort) - - if *serverRole == "slave" { - syncDataFromMaster() - go syncWithMaster() + if ovpnAdmin.role == "slave" { + ovpnAdmin.syncDataFromMaster() + go ovpnAdmin.syncWithMaster() } fs := CacheControlWrapper(http.FileServer(http.Dir(*staticPath))) http.Handle("/", fs) - http.HandleFunc("/api/server/role", serverRoleHandler) - http.HandleFunc("/api/users/list", userListHandler) - http.HandleFunc("/api/user/create", userCreateHandler) - http.HandleFunc("/api/user/revoke", userRevokeHandler) - http.HandleFunc("/api/user/unrevoke", userUnrevokeHandler) - http.HandleFunc("/api/user/config/show", userShowConfigHandler) - http.HandleFunc("/api/user/disconnect", userDisconnectHandler) - http.HandleFunc("/api/user/ccd", userShowCcdHandler) - http.HandleFunc("/api/user/ccd/apply", userApplyCcdHandler) + 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/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", lastSyncTimeHandler) - http.HandleFunc("/api/sync/last/successful", lastSuccessfulSyncTimeHandler) - http.HandleFunc(downloadCertsApiUrl, downloadCertsHandler) - http.HandleFunc(downloadCcdApiUrl, downloadCddHandler) + 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.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{})) + http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "pong") + }) + + fmt.Println("Bind: http://" + *listenHost + ":" + *listenPort) log.Fatal(http.ListenAndServe(*listenHost + ":" + *listenPort, nil)) } @@ -281,6 +397,38 @@ func CacheControlWrapper(h http.Handler) http.Handler { }) } +func (oAdmin *OpenvpnAdmin) registerMetrics() { + oAdmin.promRegistry.MustRegister(ovpnServerCertExpire) + oAdmin.promRegistry.MustRegister(ovpnServerCaCertExpire) + oAdmin.promRegistry.MustRegister(ovpnClientsTotal) + oAdmin.promRegistry.MustRegister(ovpnClientsRevoked) + oAdmin.promRegistry.MustRegister(ovpnClientsConnected) + 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 *OpenvpnAdmin) setState() { + oAdmin.activeClients = oAdmin.mgmtGetActiveClients() + oAdmin.clients = oAdmin.usersList() + + ovpnServerCaCertExpire.Set(float64(getOpvnCaCertExpireDate().Unix() - time.Now().Unix() / 3600 / 24)) +} + +func (oAdmin *OpenvpnAdmin) updateState() { + for { + time.Sleep(time.Duration(28) * time.Second) + ovpnClientBytesSent.Reset() + ovpnClientBytesReceived.Reset() + ovpnClientConnectionFrom.Reset() + ovpnClientConnectionInfo.Reset() + go oAdmin.setState() + } +} + func indexTxtParser(txt string) []indexTxtLine { var indexTxt []indexTxtLine @@ -298,6 +446,7 @@ func indexTxtParser(txt string) []indexTxtLine { } } } + return indexTxt } @@ -315,7 +464,7 @@ func renderIndexTxt(data []indexTxtLine) string { return indexTxt } -func renderClientConfig(username string) string { +func (oAdmin *OpenvpnAdmin) renderClientConfig(username string) string { if checkUserExist(username) { var hosts []OpenvpnServer @@ -333,18 +482,21 @@ func renderClientConfig(username string) string { t, _ := template.ParseFiles("client.conf.tpl") var tmp bytes.Buffer - t.Execute(&tmp, conf) + err := t.Execute(&tmp, conf) + if err != nil { + log.Printf("WARNING: something goes wrong during rendering config for %s", username ) + } hosts = nil fmt.Printf("%+v\n", tmp.String()) - return (fmt.Sprintf("%+v\n", tmp.String())) + return fmt.Sprintf("%+v\n", tmp.String()) } fmt.Printf("User \"%s\" not found", username) return fmt.Sprintf("User \"%s\" not found", username) } -func parseCcd(username string) Ccd { +func (oAdmin *OpenvpnAdmin) parseCcd(username string) Ccd { ccd := Ccd{} ccd.User = username ccd.ClientAddress = "dynamic" @@ -367,7 +519,7 @@ func parseCcd(username string) Ccd { return ccd } -func modifyCcd(ccd Ccd) (bool, string) { +func (oAdmin *OpenvpnAdmin) modifyCcd(ccd Ccd) (bool, string) { ccdErr := "something goes wrong" if fCreate(*ccdDir + "/" + ccd.User) { @@ -446,14 +598,14 @@ func validateCcd(ccd Ccd) (bool, string) { return true, ccdErr } -func getCcd(username string) Ccd { +func (oAdmin *OpenvpnAdmin) getCcd(username string) Ccd { ccd := Ccd{} ccd.User = username ccd.ClientAddress = "dynamic" ccd.CustomRoutes = []ccdRoute{} if fCreate(*ccdDir + "/" + username) { - ccd = parseCcd(username) + ccd = oAdmin.parseCcd(username) } return ccd } @@ -481,33 +633,64 @@ func checkUserExist(username string) bool { return false } -func usersList() []OpenvpnClient { +func (oAdmin *OpenvpnAdmin) usersList() []OpenvpnClient { var users []OpenvpnClient - activeClients := mgmtGetActiveClients() + + totalCerts := 0 + validCerts := 0 + revokedCerts := 0 + expiredCerts := 0 + connectedUsers := 0 + apochNow := time.Now().Unix() for _, line := range indexTxtParser(fRead(*indexTxtPath)) { if line.Identity != "server" { - ovpnClient := OpenvpnClient{Identity: line.Identity, ExpirationDate: indexTxtDateToHumanReadable(line.ExpirationDate)} + totalCerts += 1 + ovpnClient := OpenvpnClient{Identity: line.Identity, ExpirationDate: parseDateToString(indexTxtDateLayout, line.ExpirationDate, stringDateFormat)} switch { case line.Flag == "V": ovpnClient.AccountStatus = "Active" - case line.Flag == "R": + ovpnClientCertificateExpire.WithLabelValues(line.Identity).Set(float64(parseDateToUnix(indexTxtDateLayout, line.ExpirationDate) - apochNow / 3600 / 24)) + validCerts += 1 + case line.Flag == "R": ovpnClient.AccountStatus = "Revoked" - ovpnClient.RevocationDate = indexTxtDateToHumanReadable(line.RevocationDate) + ovpnClient.RevocationDate = parseDateToString(indexTxtDateLayout, line.RevocationDate, stringDateFormat) + ovpnClientCertificateExpire.WithLabelValues(line.Identity).Set(float64(parseDateToUnix(indexTxtDateLayout, line.ExpirationDate) - apochNow / 3600 / 24)) + revokedCerts += 1 case line.Flag == "E": ovpnClient.AccountStatus = "Expired" + ovpnClientCertificateExpire.WithLabelValues(line.Identity).Set(float64(parseDateToUnix(indexTxtDateLayout, line.ExpirationDate) - apochNow / 3600 / 24)) + expiredCerts += 1 } - if isUserConnected(line.Identity, activeClients) { + + if isUserConnected(line.Identity, oAdmin.activeClients) { ovpnClient.ConnectionStatus = "Connected" + connectedUsers += 1 } + users = append(users, ovpnClient) - } + + } else { + ovpnServerCertExpire.Set(float64(parseDateToUnix(indexTxtDateLayout,line.ExpirationDate) - apochNow / 3600 / 24)) + } } + + otherCerts := totalCerts - validCerts - revokedCerts - expiredCerts + + if otherCerts != 0 { + log.Printf("WARNING: there are %d otherCerts", otherCerts) + } + + ovpnClientsTotal.Set(float64(totalCerts)) + ovpnClientsRevoked.Set(float64(revokedCerts)) + ovpnClientsExpired.Set(float64(expiredCerts)) + ovpnClientsConnected.Set(float64(connectedUsers)) + return users } -func userCreate(username string) (bool, string) { - ucErr := "" +func (oAdmin *OpenvpnAdmin) userCreate(username 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) @@ -525,21 +708,35 @@ func userCreate(username string) (bool, string) { } o := runBash(fmt.Sprintf("date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && easyrsa build-client-full %s nopass", *easyrsaDirPath, username)) fmt.Println(o) - return true, fmt.Sprintf("User \"%s\" created", username) + if *debug { + log.Printf("INFO: user created: %s", username) + } + oAdmin.clients = oAdmin.usersList() + return true, ucErr } -func userRevoke(username string) string { +func (oAdmin *OpenvpnAdmin) getUserStatistic(username string) clientStatus { + for _, u := range oAdmin.activeClients { + if u.CommonName == username { + return u + } + } + return clientStatus{} +} + +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)) crlFix() + oAdmin.clients = oAdmin.usersList() return fmt.Sprintln(o) } fmt.Printf("User \"%s\" not found", username) return fmt.Sprintf("User \"%s\" not found", username) } -func userUnrevoke(username string) string { +func (oAdmin *OpenvpnAdmin) userUnrevoke(username string) string { if checkUserExist(username) { // check certificate revoked flag 'R' usersFromIndexTxt := indexTxtParser(fRead(*indexTxtPath)) @@ -549,18 +746,20 @@ func userUnrevoke(username string) string { 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) + //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) + //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) + //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) + //fmt.Println(o) fWrite(*indexTxtPath, renderIndexTxt(usersFromIndexTxt)) - fmt.Print(renderIndexTxt(usersFromIndexTxt)) + //fmt.Print(renderIndexTxt(usersFromIndexTxt)) o = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl", *easyrsaDirPath)) - fmt.Println(o) + //fmt.Println(o) crlFix() + o = "" + fmt.Println(o) break } } @@ -568,6 +767,7 @@ func userUnrevoke(username string) string { fWrite(*indexTxtPath, renderIndexTxt(usersFromIndexTxt)) fmt.Print(renderIndexTxt(usersFromIndexTxt)) crlFix() + oAdmin.clients = oAdmin.usersList() return fmt.Sprintf("{\"msg\":\"User %s successfully unrevoked\"}", username) } return fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username) @@ -579,14 +779,14 @@ func userChangePassword(username string, newPassword string) bool { return false } -func ovpnMgmtRead(conn net.Conn) string { +func (oAdmin *OpenvpnAdmin) mgmtRead(conn net.Conn) string { buf := make([]byte, 32768) bufLen, _ := conn.Read(buf) s := string(buf[:bufLen]) return s } -func mgmtConnectedUsersParser(text string) []clientStatus { +func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text string) []clientStatus { var u []clientStatus isClientList := false isRouteTable := false @@ -611,7 +811,20 @@ func mgmtConnectedUsersParser(text string) []clientStatus { } if isClientList { user := strings.Split(txt, ",") - u = append(u, clientStatus{CommonName: user[0], RealAddress: user[1], BytesReceived: user[2], BytesSent: user[3], ConnectedSince: user[4]}) + + userName := user[0] + userAddress := user[1] + userBytesRecieved:= user[2] + userBytesSent:= user[3] + userConnectedSince := user[4] + + userStatus := clientStatus{CommonName: userName, RealAddress: userAddress, BytesReceived: userBytesRecieved, BytesSent: userBytesSent, ConnectedSince: userConnectedSince} + u = append(u, userStatus) + bytesSent, _ := strconv.Atoi(userBytesSent) + bytesReceive, _ := strconv.Atoi(userBytesRecieved) + ovpnClientConnectionFrom.WithLabelValues(userName, userAddress).Set(float64(parseDateToUnix(ovpnStatusDateLayout, userConnectedSince))) + ovpnClientBytesSent.WithLabelValues(userName).Set(float64(bytesSent)) + ovpnClientBytesReceived.WithLabelValues(userName).Set(float64(bytesReceive)) } if isRouteTable { user := strings.Split(txt, ",") @@ -619,6 +832,7 @@ func mgmtConnectedUsersParser(text string) []clientStatus { if u[i].CommonName == user[1] { u[i].VirtualAddress = user[0] u[i].LastRef = user[3] + ovpnClientConnectionInfo.WithLabelValues(user[1], user[0]).Set(float64(parseDateToUnix(ovpnStatusDateLayout, user[3]))) break } } @@ -627,27 +841,27 @@ func mgmtConnectedUsersParser(text string) []clientStatus { return u } -func mgmtKillUserConnection(username string) { +func (oAdmin *OpenvpnAdmin) mgmtKillUserConnection(username string) { conn, err := net.Dial("tcp", *mgmtListenHost+":"+*mgmtListenPort) if err != nil { log.Println("ERROR: openvpn mgmt interface is not reachable") return } - ovpnMgmtRead(conn) // read welcome message + oAdmin.mgmtRead(conn) // read welcome message conn.Write([]byte(fmt.Sprintf("kill %s\n", username))) - fmt.Printf("%v", ovpnMgmtRead(conn)) + fmt.Printf("%v", oAdmin.mgmtRead(conn)) conn.Close() } -func mgmtGetActiveClients() []clientStatus { +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{} } - ovpnMgmtRead(conn) // read welcome message + oAdmin.mgmtRead(conn) // read welcome message conn.Write([]byte("status\n")) - activeClients := mgmtConnectedUsersParser(ovpnMgmtRead(conn)) + activeClients := oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn)) conn.Close() return activeClients } @@ -661,11 +875,11 @@ func isUserConnected(username string, connectedUsers []clientStatus) bool { return false } -func downloadCerts() bool { +func (oAdmin *OpenvpnAdmin) downloadCerts() bool { if fExist(certsArchivePath) { fDelete(certsArchivePath) } - err := fDownload(certsArchivePath, *masterHost + downloadCertsApiUrl + "?token=" + *masterSyncToken, masterHostBasicAuth) + err := fDownload(certsArchivePath, *masterHost + downloadCertsApiUrl + "?token=" + oAdmin.masterSyncToken, oAdmin.masterHostBasicAuth) if err != nil { log.Println(err) return false @@ -674,12 +888,12 @@ func downloadCerts() bool { return true } -func downloadCcd() bool { +func (oAdmin *OpenvpnAdmin) downloadCcd() bool { if fExist(ccdArchivePath) { fDelete(ccdArchivePath) } - err := fDownload(ccdArchivePath, *masterHost + downloadCcdApiUrl + "?token=" + *masterSyncToken, masterHostBasicAuth) + err := fDownload(ccdArchivePath, *masterHost + downloadCcdApiUrl + "?token=" + oAdmin.masterSyncToken, oAdmin.masterHostBasicAuth) if err != nil { log.Println(err) return false @@ -710,19 +924,17 @@ func unArchiveCcd() { fmt.Println(o) } -func syncDataFromMaster() { - log.Println("Downloading archives from master") +func (oAdmin *OpenvpnAdmin) syncDataFromMaster() { retryCountMax := 3 - certsDownloadFailed := true ccdDownloadFailed := true - certsDownloadRetries := 0 ccdDownloadRetries := 0 for certsDownloadFailed && certsDownloadRetries < retryCountMax { certsDownloadRetries += 1 - if downloadCerts() { + log.Printf("Downloading certs archive from master. Attempt %d", certsDownloadRetries) + if oAdmin.downloadCerts() { certsDownloadFailed = false log.Println("Decompression certs archive from master") unArchiveCerts() @@ -733,7 +945,8 @@ func syncDataFromMaster() { for ccdDownloadFailed && ccdDownloadRetries < retryCountMax { ccdDownloadRetries += 1 - if downloadCcd() { + log.Printf("Downloading ccd archive from master. Attempt %d", ccdDownloadRetries) + if oAdmin.downloadCcd() { ccdDownloadFailed = false log.Println("Decompression ccd archive from master") unArchiveCcd() @@ -742,24 +955,41 @@ func syncDataFromMaster() { } } - lastSyncTime = time.Now().Format("2006-01-02 15:04:05") + oAdmin.lastSyncTime = time.Now().Format("2006-01-02 15:04:05") if !ccdDownloadFailed && !certsDownloadFailed { - lastSuccessfulSyncTime = time.Now().Format("2006-01-02 15:04:05") + oAdmin.lastSuccessfulSyncTime = time.Now().Format("2006-01-02 15:04:05") } } -func syncWithMaster() { +func (oAdmin *OpenvpnAdmin) syncWithMaster() { for { time.Sleep(time.Duration(*masterSyncFrequency) * time.Second) - syncDataFromMaster() + oAdmin.syncDataFromMaster() } } +func getOpvnCaCertExpireDate() time.Time { + 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", err) + return time.Now() + } + + return t +} + // https://community.openvpn.net/openvpn/ticket/623 func crlFix() { - os.Chmod(*easyrsaDirPath + "/pki", 0755) - err := os.Chmod(*easyrsaDirPath + "/pki/crl.pem", 0644) - if err != nil { - log.Println(err) + err1 := os.Chmod(*easyrsaDirPath + "/pki", 0755) + if err1 != nil { + log.Println(err1) } -} \ No newline at end of file + err2 := os.Chmod(*easyrsaDirPath + "/pki/crl.pem", 0644) + if err2 != nil { + log.Println(err2) + } +} diff --git a/werf.yaml b/werf.yaml index b893e49..7eb3952 100644 --- a/werf.yaml +++ b/werf.yaml @@ -1,4 +1,4 @@ -project: openvpn-web-ui +project: openvpn-admin configVersion: 1 deploy: helmRelease: "[[ project ]]-[[ env ]]"