diff --git a/backend/consts.go b/backend/consts.go index 7ca22e9..90ca8ee 100644 --- a/backend/consts.go +++ b/backend/consts.go @@ -11,4 +11,16 @@ const ( stringDateFormat = "2006-01-02 15:04:05" KubeNamespaceFilePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" -) + + secretCA = "openvpn-pki-ca" + secretServer = "openvpn-pki-server" + secretClientTmpl = "openvpn-pki-%d" + secretCRL = "openvpn-pki-crl" + secretIndexTxt = "openvpn-pki-index-txt" + secretDHandTA = "openvpn-pki-dh-and-ta" + certFileName = "tls.crt" + privKeyFileName = "tls.key" + + //Z + indexTxtDateFormat = "060102150405Z" +) \ No newline at end of file diff --git a/backend/kubernetes.go b/backend/kubernetes.go index 2caa107..2fa4680 100644 --- a/backend/kubernetes.go +++ b/backend/kubernetes.go @@ -3,18 +3,13 @@ package backend import ( "bytes" "context" - "crypto/rsa" - "crypto/x509" "errors" "fmt" "github.com/dgryski/dgoogauth" - "github.com/google/uuid" + // "github.com/google/uuid" "golang.org/x/crypto/bcrypt" - "io/ioutil" "os" - "os/exec" "strings" - "time" log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" @@ -23,52 +18,11 @@ import ( "k8s.io/client-go/rest" ) -const ( - secretCA = "openvpn-pki-ca" - secretServer = "openvpn-pki-server" - secretClientTmpl = "openvpn-pki-%d" - secretCRL = "openvpn-pki-crl" - secretIndexTxt = "openvpn-pki-index-txt" - secretDHandTA = "openvpn-pki-dh-and-ta" - certFileName = "tls.crt" - privKeyFileName = "tls.key" -) - -//Z -const indexTxtDateFormat = "060102150405Z" - var namespace = "default" -type OpenVPNPKI struct { - CAPrivKeyRSA *rsa.PrivateKey - CAPrivKeyPEM *bytes.Buffer - CACert *x509.Certificate - CACertPEM *bytes.Buffer - ServerPrivKeyRSA *rsa.PrivateKey - ServerPrivKeyPEM *bytes.Buffer - ServerCert *x509.Certificate - ServerCertPEM *bytes.Buffer - ClientCerts []ClientCert - RevokedCerts []RevokedCert - KubeClient *kubernetes.Clientset -} - -type ClientCert struct { - PrivKeyRSA *rsa.PrivateKey - PrivKeyPEM *bytes.Buffer - Cert *x509.Certificate - CertPEM *bytes.Buffer -} - -type RevokedCert struct { - RevokedTime time.Time `json:"revokedTime"` - CommonName string `json:"commonName"` - Cert *x509.Certificate `json:"cert"` -} - -func (openVPNPKI *OpenVPNPKI) Run() (err error) { +func (openVPNPKI *OpenVPNPKI) KubeRun() (err error) { if _, err := os.Stat(KubeNamespaceFilePath); err == nil { - file, err := ioutil.ReadFile(KubeNamespaceFilePath) + file, err := os.ReadFile(KubeNamespaceFilePath) if err != nil { return err } @@ -80,7 +34,7 @@ func (openVPNPKI *OpenVPNPKI) Run() (err error) { return } - err = openVPNPKI.initPKI() + err = openVPNPKI.InitPKI() if err != nil { return } @@ -90,18 +44,6 @@ func (openVPNPKI *OpenVPNPKI) Run() (err error) { log.Error(err) } - err = openVPNPKI.easyrsaGenCRL() - if err != nil { - log.Error(err) - } - - if res, _ := openVPNPKI.secretCheckExists(secretDHandTA); !res { - err := openVPNPKI.secretGenTaKeyAndDHParam() - if err != nil { - log.Error(err) - } - } - err = openVPNPKI.updateFilesFromSecrets() if err != nil { log.Error(err) @@ -131,243 +73,243 @@ func (openVPNPKI *OpenVPNPKI) initKubeClient() (err error) { return } -func (openVPNPKI *OpenVPNPKI) initPKI() (err error) { - if res, _ := openVPNPKI.secretCheckExists(secretCA); res { - cert, err := openVPNPKI.secretGetClientCert(secretCA) - if err != nil { - return err - } +// func (openVPNPKI *OpenVPNPKI) initPKI() (err error) { +// if res, _ := openVPNPKI.secretCheckExists(secretCA); res { +// cert, err := openVPNPKI.secretGetClientCert(secretCA) +// if err != nil { +// return err +// } - openVPNPKI.CAPrivKeyPEM = cert.PrivKeyPEM - openVPNPKI.CAPrivKeyRSA = cert.PrivKeyRSA - openVPNPKI.CACertPEM = cert.CertPEM - openVPNPKI.CACert = cert.Cert - } else { - openVPNPKI.CAPrivKeyPEM, err = GenPrivKey() - if err != nil { - return - } - openVPNPKI.CAPrivKeyRSA, err = DecodePrivKey(openVPNPKI.CAPrivKeyPEM.Bytes()) +// openVPNPKI.CAPrivKeyPEM = cert.PrivKeyPEM +// openVPNPKI.CAPrivKeyRSA = cert.PrivKeyRSA +// openVPNPKI.CACertPEM = cert.CertPEM +// openVPNPKI.CACert = cert.Cert +// } else { +// openVPNPKI.CAPrivKeyPEM, err = GenPrivKey() +// if err != nil { +// return +// } +// openVPNPKI.CAPrivKeyRSA, err = DecodePrivKey(openVPNPKI.CAPrivKeyPEM.Bytes()) - openVPNPKI.CACertPEM, _ = GenCA(openVPNPKI.CAPrivKeyRSA) - openVPNPKI.CACert, err = DecodeCert(openVPNPKI.CACertPEM.Bytes()) - if err != nil { - return - } +// openVPNPKI.CACertPEM, _ = GenCA(openVPNPKI.CAPrivKeyRSA) +// openVPNPKI.CACert, err = DecodeCert(openVPNPKI.CACertPEM.Bytes()) +// if err != nil { +// return +// } - secretMetaData := metav1.ObjectMeta{Name: secretCA} +// secretMetaData := metav1.ObjectMeta{Name: secretCA} - secretData := map[string][]byte{ - certFileName: openVPNPKI.CACertPEM.Bytes(), - privKeyFileName: openVPNPKI.CAPrivKeyPEM.Bytes(), - } +// secretData := map[string][]byte{ +// certFileName: openVPNPKI.CACertPEM.Bytes(), +// privKeyFileName: openVPNPKI.CAPrivKeyPEM.Bytes(), +// } - err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS) - if err != nil { - return - } - } +// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS) +// if err != nil { +// return +// } +// } - if res, _ := openVPNPKI.secretCheckExists(secretServer); res { - cert, err := openVPNPKI.secretGetClientCert(secretServer) - if err != nil { - return err - } +// if res, _ := openVPNPKI.secretCheckExists(secretServer); res { +// cert, err := openVPNPKI.secretGetClientCert(secretServer) +// if err != nil { +// return err +// } - openVPNPKI.ServerPrivKeyPEM = cert.PrivKeyPEM - openVPNPKI.ServerPrivKeyRSA = cert.PrivKeyRSA - openVPNPKI.ServerCertPEM = cert.CertPEM - openVPNPKI.ServerCert = cert.Cert - } else { - openVPNPKI.ServerPrivKeyPEM, err = GenPrivKey() - if err != nil { - return - } +// openVPNPKI.ServerPrivKeyPEM = cert.PrivKeyPEM +// openVPNPKI.ServerPrivKeyRSA = cert.PrivKeyRSA +// openVPNPKI.ServerCertPEM = cert.CertPEM +// openVPNPKI.ServerCert = cert.Cert +// } else { +// openVPNPKI.ServerPrivKeyPEM, err = GenPrivKey() +// if err != nil { +// return +// } - openVPNPKI.ServerPrivKeyRSA, err = DecodePrivKey(openVPNPKI.ServerPrivKeyPEM.Bytes()) - if err != nil { - return - } +// openVPNPKI.ServerPrivKeyRSA, err = DecodePrivKey(openVPNPKI.ServerPrivKeyPEM.Bytes()) +// if err != nil { +// return +// } - openVPNPKI.ServerCertPEM, _ = GenServerCert(openVPNPKI.ServerPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, "server") - openVPNPKI.ServerCert, err = DecodeCert(openVPNPKI.ServerCertPEM.Bytes()) +// openVPNPKI.ServerCertPEM, _ = GenServerCert(openVPNPKI.ServerPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, "server") +// openVPNPKI.ServerCert, err = DecodeCert(openVPNPKI.ServerCertPEM.Bytes()) - secretMetaData := metav1.ObjectMeta{ - Name: secretServer, - Labels: map[string]string{ - "index.txt": "", - "name": "server", - "type": "serverAuth", - }, - } +// secretMetaData := metav1.ObjectMeta{ +// Name: secretServer, +// Labels: map[string]string{ +// "index.txt": "", +// "name": "server", +// "type": "serverAuth", +// }, +// } - secretData := map[string][]byte{ - certFileName: openVPNPKI.ServerCertPEM.Bytes(), - privKeyFileName: openVPNPKI.ServerPrivKeyPEM.Bytes(), - } +// secretData := map[string][]byte{ +// certFileName: openVPNPKI.ServerCertPEM.Bytes(), +// privKeyFileName: openVPNPKI.ServerPrivKeyPEM.Bytes(), +// } - err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS) - if err != nil { - return - } - } +// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS) +// if err != nil { +// return +// } +// } - return -} +// return +// } -func (openVPNPKI *OpenVPNPKI) indexTxtUpdate() (err error) { - secrets, err := openVPNPKI.secretsGetByLabels("index.txt=") - if err != nil { - return - } +// func (openVPNPKI *OpenVPNPKI) indexTxtUpdate() (err error) { +// secrets, err := openVPNPKI.secretsGetByLabels("index.txt=") +// if err != nil { +// return +// } - var indexTxt string - for _, secret := range secrets.Items { - certPEM := bytes.NewBuffer(secret.Data[certFileName]) - log.Trace("indexTxtUpdate:" + secret.Name) - cert, err := DecodeCert(certPEM.Bytes()) - if err != nil { - return nil - } +// var indexTxt string +// for _, secret := range secrets.Items { +// certPEM := bytes.NewBuffer(secret.Data[certFileName]) +// log.Trace("indexTxtUpdate:" + secret.Name) +// cert, err := DecodeCert(certPEM.Bytes()) +// if err != nil { +// return nil +// } - log.Trace(cert.Subject.CommonName) +// log.Trace(cert.Subject.CommonName) - if secret.Annotations["revokedAt"] == "" { - indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "V", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) - } else if cert.NotAfter.Before(time.Now()) { - indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "E", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) - } else { - indexTxt += fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", "R", cert.NotAfter.Format(indexTxtDateFormat), secret.Annotations["revokedAt"], fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) - } +// if secret.Annotations["revokedAt"] == "" { +// indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "V", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) +// } else if cert.NotAfter.Before(time.Now()) { +// indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "E", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) +// } else { +// indexTxt += fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", "R", cert.NotAfter.Format(indexTxtDateFormat), secret.Annotations["revokedAt"], fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) +// } - } +// } - secretMetaData := metav1.ObjectMeta{Name: secretIndexTxt} +// secretMetaData := metav1.ObjectMeta{Name: secretIndexTxt} - secretData := map[string][]byte{"index.txt": []byte(indexTxt)} +// secretData := map[string][]byte{"index.txt": []byte(indexTxt)} - if res, _ := openVPNPKI.secretCheckExists(secretIndexTxt); !res { - err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) - } else { - err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque) - } +// if res, _ := openVPNPKI.secretCheckExists(secretIndexTxt); !res { +// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) +// } else { +// err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque) +// } - return -} +// return +// } func (openVPNPKI *OpenVPNPKI) updateIndexTxtOnDisk() (err error) { secret, err := openVPNPKI.secretGetByName(secretIndexTxt) indexTxt := secret.Data["index.txt"] - err = ioutil.WriteFile(fmt.Sprintf("%s/pki/index.txt", *EasyrsaDirPath), indexTxt, 0600) + err = fWriteRaw(fmt.Sprintf("%s/pki/index.txt", *EasyrsaDirPath), indexTxt, 0600) return } -func (openVPNPKI *OpenVPNPKI) easyrsaGenCRL() (err error) { - err = openVPNPKI.indexTxtUpdate() - if err != nil { - return - } +// func (openVPNPKI *OpenVPNPKI) easyrsaGenCRL() (err error) { +// err = openVPNPKI.indexTxtUpdate() +// if err != nil { +// return +// } - secrets, err := openVPNPKI.secretsGetByLabels("index.txt=,type=clientAuth") - if err != nil { - return - } +// secrets, err := openVPNPKI.secretsGetByLabels("index.txt=,type=clientAuth") +// if err != nil { +// return +// } - var revoked []*RevokedCert +// var revoked []*RevokedCert - for _, secret := range secrets.Items { - if secret.Annotations["revokedAt"] != "" { - revokedAt, err := time.Parse(indexTxtDateFormat, secret.Annotations["revokedAt"]) - if err != nil { - log.Warning(err) - } - cert, err := DecodeCert(secret.Data[certFileName]) - revoked = append(revoked, &RevokedCert{RevokedTime: revokedAt, Cert: cert}) - } - } +// for _, secret := range secrets.Items { +// if secret.Annotations["revokedAt"] != "" { +// revokedAt, err := time.Parse(indexTxtDateFormat, secret.Annotations["revokedAt"]) +// if err != nil { +// log.Warning(err) +// } +// cert, err := DecodeCert(secret.Data[certFileName]) +// revoked = append(revoked, &RevokedCert{RevokedTime: revokedAt, Cert: cert}) +// } +// } - crl, err := GenCRL(revoked, openVPNPKI.CACert, openVPNPKI.CAPrivKeyRSA) - if err != nil { - return - } +// crl, err := GenCRL(revoked, openVPNPKI.CACert, openVPNPKI.CAPrivKeyRSA) +// if err != nil { +// return +// } - secretMetaData := metav1.ObjectMeta{Name: secretCRL} +// secretMetaData := metav1.ObjectMeta{Name: secretCRL} - secretData := map[string][]byte{ - "crl.pem": crl.Bytes(), - } +// secretData := map[string][]byte{ +// "crl.pem": crl.Bytes(), +// } - //err = openVPNPKI.secretCreate(secretMetaData, secretData) +// //err = openVPNPKI.secretCreate(secretMetaData, secretData) - if res, _ := openVPNPKI.secretCheckExists(secretCRL); !res { - err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) - } else { - err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque) - } +// if res, _ := openVPNPKI.secretCheckExists(secretCRL); !res { +// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) +// } else { +// err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque) +// } - return -} +// return +// } -func (openVPNPKI *OpenVPNPKI) EasyrsaBuildClient(commonName string) (err error) { - // check certificate exists - _, err = openVPNPKI.secretGetByLabels("name=" + commonName) - if err == nil { - return errors.New(fmt.Sprintf("certificate for user (%s) already exists", commonName)) - } +// func (openVPNPKI *OpenVPNPKI) EasyrsaBuildClient(commonName string) (err error) { +// // check certificate exists +// _, err = openVPNPKI.secretGetByLabels("name=" + commonName) +// if err == nil { +// return errors.New(fmt.Sprintf("certificate for user (%s) already exists", commonName)) +// } - clientPrivKeyPEM, err := GenPrivKey() - if err != nil { - return - } +// clientPrivKeyPEM, err := GenPrivKey() +// if err != nil { +// return +// } - clientPrivKeyRSA, err := DecodePrivKey(clientPrivKeyPEM.Bytes()) - if err != nil { - return - } +// clientPrivKeyRSA, err := DecodePrivKey(clientPrivKeyPEM.Bytes()) +// if err != nil { +// return +// } - clientCertPEM, _ := GenClientCert(clientPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, commonName) - clientCert, err := DecodeCert(clientCertPEM.Bytes()) +// clientCertPEM, _ := GenClientCert(clientPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, commonName) +// clientCert, err := DecodeCert(clientCertPEM.Bytes()) - secretMetaData := metav1.ObjectMeta{ - Name: fmt.Sprintf(secretClientTmpl, clientCert.SerialNumber), - Labels: map[string]string{ - "index.txt": "", - "type": "clientAuth", - "name": commonName, - "app.kubernetes.io/managed-by": "ovpn-admin", - }, - Annotations: map[string]string{ - "commonName": commonName, - "notBefore": clientCert.NotBefore.Format(indexTxtDateFormat), - "notAfter": clientCert.NotAfter.Format(indexTxtDateFormat), - "revokedAt": "", - "serialNumber": fmt.Sprintf("%d", clientCert.SerialNumber), - }, - } +// secretMetaData := metav1.ObjectMeta{ +// Name: fmt.Sprintf(secretClientTmpl, clientCert.SerialNumber), +// Labels: map[string]string{ +// "index.txt": "", +// "type": "clientAuth", +// "name": commonName, +// "app.kubernetes.io/managed-by": "ovpn-admin", +// }, +// Annotations: map[string]string{ +// "commonName": commonName, +// "notBefore": clientCert.NotBefore.Format(indexTxtDateFormat), +// "notAfter": clientCert.NotAfter.Format(indexTxtDateFormat), +// "revokedAt": "", +// "serialNumber": fmt.Sprintf("%d", clientCert.SerialNumber), +// }, +// } - secretData := map[string][]byte{ - certFileName: clientCertPEM.Bytes(), - privKeyFileName: clientPrivKeyPEM.Bytes(), - } +// secretData := map[string][]byte{ +// certFileName: clientCertPEM.Bytes(), +// privKeyFileName: clientPrivKeyPEM.Bytes(), +// } - err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS) - if err != nil { - return - } +// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS) +// if err != nil { +// return +// } - err = openVPNPKI.indexTxtUpdate() - if err != nil { - return - } +// err = openVPNPKI.indexTxtUpdate() +// if err != nil { +// return +// } - err = openVPNPKI.updateIndexTxtOnDisk() +// err = openVPNPKI.updateIndexTxtOnDisk() - return -} +// return +// } -func (openVPNPKI *OpenVPNPKI) easyrsaGetCACert() string { - return openVPNPKI.CACertPEM.String() -} +// func (openVPNPKI *OpenVPNPKI) easyrsaGetCACert() string { +// return openVPNPKI.CACertPEM.String() +// } func (openVPNPKI *OpenVPNPKI) EasyrsaGetClientCert(commonName string) (cert, key string) { secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) @@ -381,148 +323,148 @@ func (openVPNPKI *OpenVPNPKI) EasyrsaGetClientCert(commonName string) (cert, key return } -func (openVPNPKI *OpenVPNPKI) EasyrsaRevoke(commonName string) (err error) { - secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) - if err != nil { - log.Error(err) - } +// func (openVPNPKI *OpenVPNPKI) EasyrsaRevoke(commonName string) (err error) { +// secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) +// if err != nil { +// log.Error(err) +// } - if secret.Annotations["revokedAt"] != "" { - log.Warnf("user (%s) already revoked", commonName) - return - } +// if secret.Annotations["revokedAt"] != "" { +// log.Warnf("user (%s) already revoked", commonName) +// return +// } - secret.Annotations["revokedAt"] = time.Now().Format(indexTxtDateFormat) +// secret.Annotations["revokedAt"] = time.Now().Format(indexTxtDateFormat) - _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - if err != nil { - return - } +// _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) +// if err != nil { +// return +// } - err = openVPNPKI.indexTxtUpdate() - if err != nil { - return - } +// err = openVPNPKI.indexTxtUpdate() +// if err != nil { +// return +// } - err = openVPNPKI.updateIndexTxtOnDisk() - if err != nil { - return - } +// err = openVPNPKI.updateIndexTxtOnDisk() +// if err != nil { +// return +// } - err = openVPNPKI.easyrsaGenCRL() - if err != nil { - log.Error(err) - } +// err = openVPNPKI.easyrsaGenCRL() +// if err != nil { +// log.Error(err) +// } - err = openVPNPKI.updateCRLOnDisk() +// err = openVPNPKI.updateCRLOnDisk() - return -} +// return +// } -func (openVPNPKI *OpenVPNPKI) EasyrsaUnrevoke(commonName string) (err error) { - secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) - if err != nil { - log.Error(err) - } +// func (openVPNPKI *OpenVPNPKI) EasyrsaUnrevoke(commonName string) (err error) { +// secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) +// if err != nil { +// log.Error(err) +// } - secret.Annotations["revokedAt"] = "" +// secret.Annotations["revokedAt"] = "" - _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - if err != nil { - return - } +// _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) +// if err != nil { +// return +// } - err = openVPNPKI.indexTxtUpdate() - if err != nil { - return - } +// err = openVPNPKI.indexTxtUpdate() +// if err != nil { +// return +// } - err = openVPNPKI.updateIndexTxtOnDisk() - if err != nil { - return - } +// err = openVPNPKI.updateIndexTxtOnDisk() +// if err != nil { +// return +// } - err = openVPNPKI.easyrsaGenCRL() - if err != nil { - log.Error(err) - } +// err = openVPNPKI.easyrsaGenCRL() +// if err != nil { +// log.Error(err) +// } - err = openVPNPKI.updateCRLOnDisk() +// err = openVPNPKI.updateCRLOnDisk() - return -} +// return +// } -func (openVPNPKI *OpenVPNPKI) EasyrsaRotate(commonName string) (err error) { - secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) - if err != nil { - log.Error(err) - } - uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) - secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash - secret.Labels["name"] = "REVOKED" + commonName - secret.Labels["revokedForever"] = "true" +// func (openVPNPKI *OpenVPNPKI) EasyrsaRotate(commonName string) (err error) { +// secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) +// if err != nil { +// log.Error(err) +// } +// uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) +// secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash +// secret.Labels["name"] = "REVOKED" + commonName +// secret.Labels["revokedForever"] = "true" - _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - if err != nil { - return - } +// _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) +// if err != nil { +// return +// } - err = openVPNPKI.EasyrsaBuildClient(commonName) - if err != nil { - return - } +// err = openVPNPKI.EasyrsaBuildClient(commonName) +// if err != nil { +// return +// } - err = openVPNPKI.indexTxtUpdate() - if err != nil { - return - } +// err = openVPNPKI.indexTxtUpdate() +// if err != nil { +// return +// } - err = openVPNPKI.updateIndexTxtOnDisk() - if err != nil { - return - } +// err = openVPNPKI.updateIndexTxtOnDisk() +// if err != nil { +// return +// } - err = openVPNPKI.easyrsaGenCRL() - if err != nil { - log.Error(err) - } +// err = openVPNPKI.easyrsaGenCRL() +// if err != nil { +// log.Error(err) +// } - err = openVPNPKI.updateCRLOnDisk() - return -} -func (openVPNPKI *OpenVPNPKI) EasyrsaDelete(commonName string) (err error) { - secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) - if err != nil { - log.Error(err) - } - uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) - secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash - secret.Labels["name"] = "REVOKED-" + commonName + "-" + uniqHash - secret.Labels["revokedForever"] = "true" +// err = openVPNPKI.updateCRLOnDisk() +// return +// } +// func (openVPNPKI *OpenVPNPKI) EasyrsaDelete(commonName string) (err error) { +// secret, err := openVPNPKI.secretGetByLabels("name=" + commonName) +// if err != nil { +// log.Error(err) +// } +// uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) +// secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash +// secret.Labels["name"] = "REVOKED-" + commonName + "-" + uniqHash +// secret.Labels["revokedForever"] = "true" - _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - if err != nil { - return - } +// _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) +// if err != nil { +// return +// } - err = openVPNPKI.indexTxtUpdate() - if err != nil { - return - } +// err = openVPNPKI.indexTxtUpdate() +// if err != nil { +// return +// } - err = openVPNPKI.updateIndexTxtOnDisk() - if err != nil { - return - } +// err = openVPNPKI.updateIndexTxtOnDisk() +// if err != nil { +// return +// } - err = openVPNPKI.easyrsaGenCRL() - if err != nil { - log.Error(err) - } +// err = openVPNPKI.easyrsaGenCRL() +// if err != nil { +// log.Error(err) +// } - err = openVPNPKI.updateCRLOnDisk() - return -} +// err = openVPNPKI.updateCRLOnDisk() +// return +// } func (openVPNPKI *OpenVPNPKI) secretGetClientCert(name string) (cert ClientCert, err error) { secret, err := openVPNPKI.secretGetByName(name) @@ -546,12 +488,12 @@ func (openVPNPKI *OpenVPNPKI) secretGetClientCert(name string) (cert ClientCert, } func (openVPNPKI *OpenVPNPKI) updateFilesFromSecrets() (err error) { - ca, err := openVPNPKI.secretGetClientCert(secretCA) + ca, err := openVPNPKI.getExistCert(secretCA) if err != nil { return } - server, err := openVPNPKI.secretGetClientCert(secretServer) + server, err := openVPNPKI.getExistCert(secretServer) if err != nil { return } @@ -568,27 +510,27 @@ func (openVPNPKI *OpenVPNPKI) updateFilesFromSecrets() (err error) { err = os.MkdirAll(fmt.Sprintf("%s/pki/private", *EasyrsaDirPath), 0755) } - err = ioutil.WriteFile(fmt.Sprintf("%s/pki/ca.crt", *EasyrsaDirPath), ca.CertPEM.Bytes(), 0600) + err = os.WriteFile(fmt.Sprintf("%s/pki/ca.crt", *EasyrsaDirPath), ca.CertPEM.Bytes(), 0600) if err != nil { return } - err = ioutil.WriteFile(fmt.Sprintf("%s/pki/issued/server.crt", *EasyrsaDirPath), server.CertPEM.Bytes(), 0600) + err = os.WriteFile(fmt.Sprintf("%s/pki/issued/server.crt", *EasyrsaDirPath), server.CertPEM.Bytes(), 0600) if err != nil { return } - err = ioutil.WriteFile(fmt.Sprintf("%s/pki/private/server.key", *EasyrsaDirPath), server.PrivKeyPEM.Bytes(), 0600) + err = os.WriteFile(fmt.Sprintf("%s/pki/private/server.key", *EasyrsaDirPath), server.PrivKeyPEM.Bytes(), 0600) if err != nil { return } - err = ioutil.WriteFile(fmt.Sprintf("%s/pki/ta.key", *EasyrsaDirPath), takey, 0600) + err = os.WriteFile(fmt.Sprintf("%s/pki/ta.key", *EasyrsaDirPath), takey, 0600) if err != nil { return } - err = ioutil.WriteFile(fmt.Sprintf("%s/pki/dh.pem", *EasyrsaDirPath), dhparam, 0600) + err = os.WriteFile(fmt.Sprintf("%s/pki/dh.pem", *EasyrsaDirPath), dhparam, 0600) if err != nil { return } @@ -600,45 +542,45 @@ func (openVPNPKI *OpenVPNPKI) updateFilesFromSecrets() (err error) { func (openVPNPKI *OpenVPNPKI) updateCRLOnDisk() (err error) { secret, err := openVPNPKI.secretGetByName(secretCRL) crl := secret.Data["crl.pem"] - err = ioutil.WriteFile(fmt.Sprintf("%s/pki/crl.pem", *EasyrsaDirPath), crl, 0644) + err = os.WriteFile(fmt.Sprintf("%s/pki/crl.pem", *EasyrsaDirPath), crl, 0644) if err != nil { log.Errorf("error write crl.pem:%s", err.Error()) } return } -func (openVPNPKI *OpenVPNPKI) secretGenTaKeyAndDHParam() (err error) { - taKeyPath := "/tmp/ta.key" - cmd := exec.Command("bash", "-c", fmt.Sprintf("/usr/sbin/openvpn --genkey --secret %s", taKeyPath)) - stdout, err := cmd.CombinedOutput() - log.Info(fmt.Sprintf("/usr/sbin/openvpn --genkey --secret %s: %s", taKeyPath, string(stdout))) - if err != nil { - return - } - taKey, err := ioutil.ReadFile(taKeyPath) +// func (openVPNPKI *OpenVPNPKI) secretGenTaKeyAndDHParam() (err error) { +// taKeyPath := "/tmp/ta.key" +// cmd := exec.Command("bash", "-c", fmt.Sprintf("/usr/sbin/openvpn --genkey --secret %s", taKeyPath)) +// stdout, err := cmd.CombinedOutput() +// log.Info(fmt.Sprintf("/usr/sbin/openvpn --genkey --secret %s: %s", taKeyPath, string(stdout))) +// if err != nil { +// return +// } +// taKey, err := os.ReadFile(taKeyPath) - dhparamPath := "/tmp/dh.pem" - cmd = exec.Command("bash", "-c", fmt.Sprintf("openssl dhparam -out %s 2048", dhparamPath)) - _, err = cmd.CombinedOutput() - if err != nil { - return - } - dhparam, err := ioutil.ReadFile(dhparamPath) +// dhparamPath := "/tmp/dh.pem" +// cmd = exec.Command("bash", "-c", fmt.Sprintf("openssl dhparam -out %s 2048", dhparamPath)) +// _, err = cmd.CombinedOutput() +// if err != nil { +// return +// } +// dhparam, err := os.ReadFile(dhparamPath) - secretMetaData := metav1.ObjectMeta{Name: secretDHandTA} +// secretMetaData := metav1.ObjectMeta{Name: secretDHandTA} - secretData := map[string][]byte{ - "ta.key": taKey, - "dh.pem": dhparam, - } +// secretData := map[string][]byte{ +// "ta.key": taKey, +// "dh.pem": dhparam, +// } - err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) - if err != nil { - return - } +// err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) +// if err != nil { +// return +// } - return -} +// return +// } // ccd @@ -690,7 +632,7 @@ func (openVPNPKI *OpenVPNPKI) updateCcdOnDisk() error { for _, secret := range secrets.Items { ccd := secret.Data["ccd"] if len(ccd) > 0 { - err = ioutil.WriteFile(fmt.Sprintf("%s/%s", *CcdDir, secret.Labels["name"]), ccd, 0644) + err = os.WriteFile(fmt.Sprintf("%s/%s", *CcdDir, secret.Labels["name"]), ccd, 0644) if err != nil { log.Error(err) } diff --git a/backend/methods.go b/backend/methods.go index 5d5a002..607adcd 100644 --- a/backend/methods.go +++ b/backend/methods.go @@ -14,7 +14,6 @@ import ( "text/template" "time" - "github.com/google/uuid" ou "github.com/pashcovich/openvpn-user/src" log "github.com/sirupsen/logrus" ) @@ -280,7 +279,7 @@ func (oAdmin *OvpnAdmin) usersList() []OpenvpnClient { apochNow := time.Now().Unix() for _, line := range IndexTxtParser(fRead(*IndexTxtPath)) { - if line.Identity != "server" && !strings.Contains(line.Identity, "REVOKED") { + if line.Identity != "server" && line.Identity != "ca" && !strings.Contains(line.Identity, "REVOKED") { totalCerts += 1 ovpnClient := OpenvpnClient{Identity: line.Identity, ExpirationDate: parseDateToString(indexTxtDateLayout, line.ExpirationDate, stringDateFormat)} switch { @@ -365,33 +364,28 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (string, error) { } } - if *StorageBackend == "kubernetes.secrets" { - err := oAdmin.KubeClient.EasyrsaBuildClient(username) - if err != nil { - log.Error(err) - return err.Error(), err - } - if oAdmin.ExtraAuth { + err := oAdmin.PKI.BuildKeyPairClient(username) + if err != nil { + log.Error(err) + return err.Error(), err + } + + if oAdmin.ExtraAuth { + switch *StorageBackend { + case "kubernetes.secrets": err = oAdmin.KubeClient.updatePasswordSecret(username, []byte(password)) if err != nil { return err.Error(), err } - } - } else { - o := runBash(fmt.Sprintf("cd %s && %s build-client-full %s nopass 1>/dev/null", *EasyrsaDirPath, *EasyrsaBinPath, username)) - log.Debug(o) - if oAdmin.ExtraAuth { + case "filesystems": _, err := oAdmin.OUser.CreateUser(username, password) if err != nil { return err.Error(), err } } } - log.Infof("Certificate for user %s issued", username) - //oAdmin.clients = oAdmin.usersList() - return "", nil } @@ -409,13 +403,13 @@ func (oAdmin *OvpnAdmin) userChangePassword(username, password string) (error, s log.Warningf("userChangePassword: %s", err.Error()) return err, err.Error() } - - if *StorageBackend == "kubernetes.secrets" { + switch *StorageBackend { + case "kubernetes.secrets": err := oAdmin.KubeClient.updatePasswordSecret(username, []byte(password)) if err != nil { return err, err.Error() } - } else { + case "filesystem": msg, err := oAdmin.OUser.ChangeUserPassword(username, password) if err != nil { return err, msg @@ -630,19 +624,13 @@ func (oAdmin *OvpnAdmin) getUserStatistic(username string) []ClientStatus { func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) { log.Infof("Revoke certificate for user %s", username) if checkUserExist(username) { - // check certificate valid flag 'V' - if *StorageBackend == "kubernetes.secrets" { - err := oAdmin.KubeClient.EasyrsaRevoke(username) - if err != nil { - log.Error(err) - } - } else { - o := runBash(fmt.Sprintf("cd %[1]s && echo yes | %[2]s revoke %[3]s 1>/dev/null && %[2]s gen-crl 1>/dev/null", *EasyrsaDirPath, *EasyrsaBinPath, username)) - log.Debugln(o) + err := oAdmin.PKI.CertificateRevoke(username) + if err != nil { + log.Error(err) } - if oAdmin.ExtraAuth { - if oAdmin.OUser.CheckUserExistent(username) { + if *StorageBackend == "filesystem" { + if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) { revokeMsg, revokeErr := oAdmin.OUser.RevokedUser(username) log.Debug(revokeMsg) log.Debug(revokeErr) @@ -652,7 +640,6 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) { } } - crlFix() userConnected, userConnectedTo := isUserConnected(username, oAdmin.activeClients) log.Tracef("User %s connected: %t", username, userConnected) if userConnected { @@ -671,67 +658,23 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) { func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) { if checkUserExist(username) { - if *StorageBackend == "kubernetes.secrets" { - err := oAdmin.KubeClient.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 = "" + err := oAdmin.PKI.CertificateUnRevoke(username) + if err != nil { + log.Error(err) + } - err := fMove(fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/issued/%s.crt", *EasyrsaDirPath, username)) - if err != nil { - log.Error(err) - } - err = fMove(fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/certs_by_serial/%s.pem", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber)) - if err != nil { - log.Error(err) - } - err = fMove(fmt.Sprintf("%s/pki/revoked/private_by_serial/%s.key", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/private/%s.key", *EasyrsaDirPath, username)) - if err != nil { - log.Error(err) - } - err = fMove(fmt.Sprintf("%s/pki/revoked/reqs_by_serial/%s.req", *EasyrsaDirPath, usersFromIndexTxt[i].SerialNumber), fmt.Sprintf("%s/pki/reqs/%s.req", *EasyrsaDirPath, username)) - if err != nil { - log.Error(err) - } - err = fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt)) - if err != nil { - log.Error(err) - } - - _ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null", *EasyrsaDirPath, *EasyrsaBinPath)) - - if oAdmin.ExtraAuth { - if oAdmin.OUser.CheckUserExistent(username) { - restoreMsg, restoreErr := oAdmin.OUser.RestoreUser(username) - log.Debug(restoreMsg) - log.Debug(restoreErr) - if restoreErr != nil { - return restoreErr, "" - } - } - } - - crlFix() - - break - } + if *StorageBackend == "filesystem" { + if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) { + restoreMsg, restoreErr := oAdmin.OUser.RestoreUser(username) + log.Debug(restoreMsg) + log.Debug(restoreErr) + if restoreErr != nil { + return restoreErr, "" } } - err := fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt)) - if err != nil { - log.Error(err) - } } - crlFix() + oAdmin.clients = oAdmin.usersList() return nil, fmt.Sprintf("{\"msg\":\"User %s successfully unrevoked\"}", username) } @@ -740,119 +683,46 @@ func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) { func (oAdmin *OvpnAdmin) userRotate(username, newPassword string) (error, string) { if checkUserExist(username) { - if *StorageBackend == "kubernetes.secrets" { - err := oAdmin.KubeClient.EasyrsaRotate(username) - if err != nil { - log.Error(err) - } - } else { - - var oldUserIndex, newUserIndex int - var oldUserSerial string - - uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) - - usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath)) - for i := range usersFromIndexTxt { - if usersFromIndexTxt[i].DistinguishedName == "/CN="+username { - oldUserSerial = usersFromIndexTxt[i].SerialNumber - usersFromIndexTxt[i].DistinguishedName = "/CN=REVOKED-" + username + "-" + uniqHash - oldUserIndex = i - break - } - } - err := fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt)) - if err != nil { - log.Error(err) - } - - if oAdmin.ExtraAuth { - if oAdmin.OUser.CheckUserExistent(username) { - deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true) - log.Debug(deleteMsg) - log.Debug(deleteErr) - if deleteErr != nil { - return deleteErr, "" - } - log.Debug(deleteMsg) - } - } - - userCreateMessage, userCreateError := oAdmin.userCreate(username, newPassword) - if userCreateError != nil { - usersFromIndexTxt = IndexTxtParser(fRead(*IndexTxtPath)) - for i := range usersFromIndexTxt { - if usersFromIndexTxt[i].SerialNumber == oldUserSerial { - usersFromIndexTxt[i].DistinguishedName = "/CN=" + username - break - } - } - err = fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt)) - if err != nil { - log.Error(err) - } - return fmt.Errorf("error rotaing user due: %s", userCreateMessage), userCreateMessage - } - - usersFromIndexTxt = IndexTxtParser(fRead(*IndexTxtPath)) - for i := range usersFromIndexTxt { - if usersFromIndexTxt[i].DistinguishedName == "/CN="+username { - newUserIndex = i - } - if usersFromIndexTxt[i].SerialNumber == oldUserSerial { - oldUserIndex = i - } - } - usersFromIndexTxt[oldUserIndex], usersFromIndexTxt[newUserIndex] = usersFromIndexTxt[newUserIndex], usersFromIndexTxt[oldUserIndex] - - err = fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt)) - if err != nil { - log.Error(err) - } - - _ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null", *EasyrsaDirPath, *EasyrsaBinPath)) + err := oAdmin.PKI.CertificateRotate(username) + if err != nil { + log.Error(err) + } + + if *StorageBackend == "filesystem" { + if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) { + deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true) + log.Debug(deleteMsg) + log.Debug(deleteErr) + if deleteErr != nil { + return deleteErr, "" + } + log.Debug(deleteMsg) + } } - crlFix() oAdmin.clients = oAdmin.usersList() return nil, fmt.Sprintf("{\"msg\":\"User %s successfully rotated\"}", username) } return fmt.Errorf("user \"%s\" not found", username), fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username) -} + } func (oAdmin *OvpnAdmin) userDelete(username string) (error, string) { if checkUserExist(username) { - if *StorageBackend == "kubernetes.secrets" { - err := oAdmin.KubeClient.EasyrsaDelete(username) - if err != nil { - log.Error(err) - } - } else { - uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) - usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath)) - for i := range usersFromIndexTxt { - if usersFromIndexTxt[i].DistinguishedName == "/CN="+username { - usersFromIndexTxt[i].DistinguishedName = "/CN=REVOKED-" + username + "-" + uniqHash - break - } - } - if oAdmin.ExtraAuth { - if oAdmin.OUser.CheckUserExistent(username) { - deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true) - log.Debug(deleteMsg) - log.Debug(deleteErr) - if deleteErr != nil { - log.Debug(deleteErr) - return deleteErr, "" - } - } - } - err := fWrite(*IndexTxtPath, renderIndexTxt(usersFromIndexTxt)) - if err != nil { - log.Error(err) - } - _ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null ", *EasyrsaDirPath, *EasyrsaBinPath)) + err := oAdmin.PKI.CertificateDelAfterRevoke(username) + if err != nil { + log.Error(err) + } + + if *StorageBackend == "filesystem" { + if oAdmin.ExtraAuth && oAdmin.OUser.CheckUserExistent(username) { + deleteMsg, deleteErr := oAdmin.OUser.DeleteUser(username, true) + log.Debug(deleteMsg) + log.Debug(deleteErr) + if deleteErr != nil { + return deleteErr, "" + } + log.Debug(deleteMsg) + } } - crlFix() oAdmin.clients = oAdmin.usersList() return nil, fmt.Sprintf("{\"msg\":\"User %s successfully deleted\"}", username) } diff --git a/backend/models.go b/backend/models.go index 0e9d486..38bdfb1 100644 --- a/backend/models.go +++ b/backend/models.go @@ -3,7 +3,12 @@ package backend import ( "io/fs" "sync" - + "bytes" + "time" + "crypto/rsa" + "crypto/x509" + + "k8s.io/client-go/kubernetes" "github.com/pashcovich/openvpn-user/src" "github.com/prometheus/client_golang/prometheus" ) @@ -19,12 +24,13 @@ type OvpnAdmin struct { PromRegistry *prometheus.Registry OUser *src.OpenvpnUser KubeClient *OpenVPNPKI + PKI *OpenVPNPKI MgmtInterfaces map[string]string Templates fs.FS Modules []string mgmtStatusTimeFormat string CreateUserMutex *sync.Mutex - ExtraAuth bool + ExtraAuth bool } type OpenvpnServer struct { @@ -86,3 +92,32 @@ type ClientStatus struct { LastRefFormatted string ConnectedTo string } + +type OpenVPNPKI struct { + CAPrivKeyRSA *rsa.PrivateKey + CAPrivKeyPEM *bytes.Buffer + CACert *x509.Certificate + CACertPEM *bytes.Buffer + ServerPrivKeyRSA *rsa.PrivateKey + ServerPrivKeyPEM *bytes.Buffer + ServerCert *x509.Certificate + ServerCertPEM *bytes.Buffer + TaKey *bytes.Buffer + DhParam *bytes.Buffer + ClientCerts []ClientCert + RevokedCerts []RevokedCert + KubeClient *kubernetes.Clientset +} + +type ClientCert struct { + PrivKeyRSA *rsa.PrivateKey + PrivKeyPEM *bytes.Buffer + Cert *x509.Certificate + CertPEM *bytes.Buffer +} + +type RevokedCert struct { + RevokedTime time.Time `json:"revokedTime"` + CommonName string `json:"commonName"` + Cert *x509.Certificate `json:"cert"` +} \ No newline at end of file diff --git a/backend/pki.go b/backend/pki.go new file mode 100644 index 0000000..13593a8 --- /dev/null +++ b/backend/pki.go @@ -0,0 +1,1083 @@ +package backend + +import ( + "bytes" + "strings" + "context" + "errors" + "fmt" + "os" + "os/exec" + "time" + + "github.com/google/uuid" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (openVPNPKI *OpenVPNPKI) InitPKI() (err error) { + err = openVPNPKI.initCA() + if err != nil { + return err + } + + err = openVPNPKI.initSecretServer() + if err != nil { + return err + } + + err = openVPNPKI.initIndexTxt() + if err != nil { + log.Error(err) + } + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + + err = openVPNPKI.initTaKeyAndDHParam() + if err != nil { + log.Error(err) + } + + err = openVPNPKI.updateFiles() + if err != nil { + log.Error(err) + } + + + return +} + +func (openVPNPKI *OpenVPNPKI) initCA() (err error) { + if openVPNPKI.checkExistData(secretCA) { + cert, err := openVPNPKI.getExistCert(secretCA) + if err != nil { + return err + } + openVPNPKI.CAPrivKeyPEM = cert.PrivKeyPEM + openVPNPKI.CAPrivKeyRSA = cert.PrivKeyRSA + openVPNPKI.CACertPEM = cert.CertPEM + openVPNPKI.CACert = cert.Cert + } else { + cert := openVPNPKI.generateKeyPair() + openVPNPKI.CAPrivKeyPEM = cert.PrivKeyPEM + openVPNPKI.CAPrivKeyRSA = cert.PrivKeyRSA + openVPNPKI.CACertPEM = cert.CertPEM + openVPNPKI.CACert = cert.Cert + } + return +} + +func (openVPNPKI *OpenVPNPKI) generateKeyPair() (cert ClientCert) { + var err error + cert.PrivKeyPEM, err = GenPrivKey() + if err != nil { + return + } + cert.PrivKeyRSA, err = DecodePrivKey(cert.PrivKeyPEM.Bytes()) + if err != nil { + return + } + cert.CertPEM, err = GenCA(cert.PrivKeyRSA) + if err != nil { + return + } + cert.Cert, err = DecodeCert(cert.CertPEM.Bytes()) + if err != nil { + return + } + return cert +} + +func (openVPNPKI *OpenVPNPKI) initSecretServer() (err error) { + + if openVPNPKI.checkExistData(secretServer) { + cert, err := openVPNPKI.getExistCert(secretServer) + if err != nil { + return err + } + openVPNPKI.ServerPrivKeyPEM = cert.PrivKeyPEM + openVPNPKI.ServerPrivKeyRSA = cert.PrivKeyRSA + openVPNPKI.ServerCertPEM = cert.CertPEM + openVPNPKI.ServerCert = cert.Cert + } else { + cert := openVPNPKI.generateKeyPair() + openVPNPKI.ServerPrivKeyPEM = cert.PrivKeyPEM + openVPNPKI.ServerPrivKeyRSA = cert.PrivKeyRSA + openVPNPKI.ServerCertPEM = cert.CertPEM + openVPNPKI.ServerCert = cert.Cert + + if err != nil { + return err + } + } + + return +} + +func (openVPNPKI *OpenVPNPKI) checkExistData(dataName string) (res bool) { + switch *StorageBackend { + case "kubernetes.secrets": + res, _ = openVPNPKI.secretCheckExists(dataName) + case "filesystem": + if dataName == secretCA { + res = fExist(fmt.Sprintf("%s/pki/ca.crt", *EasyrsaDirPath)) + } else if dataName == secretServer{ + res = fExist(fmt.Sprintf("%s/pki/issued/server.crt", *EasyrsaDirPath)) + } + } + return res +} + +func (openVPNPKI *OpenVPNPKI) getExistCert(name string) (data ClientCert, err error) { + switch *StorageBackend { + + case "kubernetes.secrets": + data, err = openVPNPKI.secretGetClientCert(name) + + case "filesystem": + var crtPath,keyPath string + + if name == secretCA { + crtPath = fmt.Sprintf("%s/pki/ca.crt", *EasyrsaDirPath) + keyPath = fmt.Sprintf("%s/pki/private/ca.key", *EasyrsaDirPath) + } else if name == secretServer { + crtPath = fmt.Sprintf("%s/pki/issued/server.crt", *EasyrsaDirPath) + keyPath = fmt.Sprintf("%s/pki/private/server.key", *EasyrsaDirPath) + } else { + // TODO: check how used this + crtPath = fmt.Sprintf("%s/pki/issued/%s.crt", *EasyrsaDirPath, name) + } + + certData := fReadRaw(crtPath) + data.CertPEM = bytes.NewBuffer(certData) + data.Cert, err = DecodeCert(data.CertPEM.Bytes()) + if err != nil { + return + } + + if len(keyPath) > 0 { + privKeyData := fReadRaw(keyPath) + data.PrivKeyPEM = bytes.NewBuffer(privKeyData) + data.PrivKeyRSA, err = DecodePrivKey(data.PrivKeyPEM.Bytes()) + if err != nil { + return + } + } + + } + return data, err +} + +func (openVPNPKI *OpenVPNPKI) BuildKeyPairClient(commonName string) (err error) { + + switch *StorageBackend { + + case "kubernetes.secrets": + // check certificate exists + _, err = openVPNPKI.secretGetByLabels("name=" + commonName) + if err == nil { + return fmt.Errorf("certificate for user (%s) already exists", commonName) + } + + + clientPrivKeyPEM, _ := GenPrivKey() + clientPrivKeyRSA, _ := DecodePrivKey(clientPrivKeyPEM.Bytes()) + clientCertPEM, _ := GenClientCert(clientPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, commonName) + clientCert, _ := DecodeCert(clientCertPEM.Bytes()) + + secretMetaData := metav1.ObjectMeta{ + Name: fmt.Sprintf(secretClientTmpl, clientCert.SerialNumber), + Labels: map[string]string{ + "index.txt": "", + "type": "clientAuth", + "name": commonName, + "app.kubernetes.io/managed-by": "ovpn-admin", + }, + Annotations: map[string]string{ + "commonName": commonName, + "notBefore": clientCert.NotBefore.Format(indexTxtDateFormat), + "notAfter": clientCert.NotAfter.Format(indexTxtDateFormat), + "revokedAt": "", + "serialNumber": fmt.Sprintf("%d", clientCert.SerialNumber), + }, + } + + secretData := map[string][]byte{ + certFileName: clientCertPEM.Bytes(), + privKeyFileName: clientPrivKeyPEM.Bytes(), + } + + err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeTLS) + if err != nil { + return + } + + case "filesystem": + + if checkUserExist(commonName) { + return errors.New(fmt.Sprintf("certificate for user (%s) already exists", commonName)) + } + + clientPrivKeyPEM, _ := GenPrivKey() + clientPrivKeyRSA, _ := DecodePrivKey(clientPrivKeyPEM.Bytes()) + clientCertPEM, _ := GenClientCert(clientPrivKeyRSA, openVPNPKI.CAPrivKeyRSA, openVPNPKI.CACert, commonName) + clientCert, _ := DecodeCert(clientCertPEM.Bytes()) + + err = fWriteRaw(fmt.Sprintf("%s/pki/issued/%s.crt", *EasyrsaDirPath, commonName), clientCertPEM.Bytes(), 0644) + if err != nil { + return err + } + err = fWriteRaw(fmt.Sprintf("%s/pki/private/%s.key", *EasyrsaDirPath, commonName), clientPrivKeyPEM.Bytes(), 0600) + if err != nil { + return err + } + + err = fWriteRaw(fmt.Sprintf("%s/pki/certs_by_serial/%X.pem", *EasyrsaDirPath, clientCert.SerialNumber), clientCertPEM.Bytes(), 0600) + if err != nil { + return err + } + + } + + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + return err + } + + if *StorageBackend == "kubernetes.secret" { + err = openVPNPKI.updateIndexTxtOnDisk() + if err != nil { + return err + } + } + + return +} + + +func (openVPNPKI *OpenVPNPKI) initIndexTxt() (err error) { + var indexTxt string + + switch *StorageBackend { + case "kubernetes.secrets": + secrets, err := openVPNPKI.secretsGetByLabels("index.txt=") + if err != nil { + return err + } + + + for _, secret := range secrets.Items { + certPEM := bytes.NewBuffer(secret.Data[certFileName]) + log.Trace("indexTxtUpdate:" + secret.Name) + cert, err := DecodeCert(certPEM.Bytes()) + if err != nil { + return nil + } + + log.Trace(cert.Subject.CommonName) + + if secret.Annotations["revokedAt"] == "" { + indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "V", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) + } else if cert.NotAfter.Before(time.Now()) { + indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "E", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) + } else { + indexTxt += fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", "R", cert.NotAfter.Format(indexTxtDateFormat), secret.Annotations["revokedAt"], fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) + } + + } + + secretMetaData := metav1.ObjectMeta{Name: secretIndexTxt} + + secretData := map[string][]byte{"index.txt": []byte(indexTxt)} + + if res, _ := openVPNPKI.secretCheckExists(secretIndexTxt); !res { + err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) + } else { + err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque) + } + case "filesystem": + if _, err := os.Stat(fmt.Sprintf("%s/pki/issued", *EasyrsaDirPath)); os.IsNotExist(err) { + err = os.MkdirAll(fmt.Sprintf("%s/pki/issued", *EasyrsaDirPath), 0755) + return err + } + + if _, err := os.Stat(fmt.Sprintf("%s/pki/private", *EasyrsaDirPath)); os.IsNotExist(err) { + err = os.MkdirAll(fmt.Sprintf("%s/pki/private", *EasyrsaDirPath), 0755) + return err + } + + if !fExist(*EasyrsaDirPath+"/pki/index.txt") { + path := *EasyrsaDirPath+"/pki/issued/" + files := fReadDir(path) + for _, file := range files { + certData := fReadRaw(path+file.Name()) + certPEM := bytes.NewBuffer(certData) + log.Trace("indexTxtUpdate:" + file.Name()) + cert, err := DecodeCert(certPEM.Bytes()) + if err != nil { + return nil + } + log.Trace(cert.Subject.CommonName) + + if cert.NotAfter.Before(time.Now()) { + indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "E", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%X", cert.SerialNumber), "unknown", "/CN="+cert.Subject.CommonName) + } else { + indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "V", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%X", cert.SerialNumber), "unknown", "/CN="+cert.Subject.CommonName) + } + } + path = *EasyrsaDirPath+"/pki/revoked/certs_by_serial/" + if fExist(path){ + filesRevoke := fReadDir(path) + for _, file := range filesRevoke { + certData := fReadRaw(path+file.Name()) + certPEM := bytes.NewBuffer(certData) + log.Trace("indexTxtUpdate:" + file.Name()) + cert, err := DecodeCert(certPEM.Bytes()) + if err != nil { + return nil + } + log.Trace(cert.Subject.CommonName) + + indexTxt += fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", "R", cert.NotAfter.Format(indexTxtDateFormat), time.Now().Format(indexTxtDateFormat), fmt.Sprintf("%X", cert.SerialNumber), "unknown", "/CN=REVOKED-"+cert.Subject.CommonName) + } + } + err = fWrite(*EasyrsaDirPath+"/pki/index.txt", indexTxt) + if err != nil { + return err + } + } + } + return +} + + +func (openVPNPKI *OpenVPNPKI) indexTxtUpdate() (err error) { + + switch *StorageBackend { + case "kubernetes.secrets": + var indexTxt string + secrets, err := openVPNPKI.secretsGetByLabels("index.txt=") + if err != nil { + return err + } + + for _, secret := range secrets.Items { + certPEM := bytes.NewBuffer(secret.Data[certFileName]) + log.Trace("indexTxtUpdate:" + secret.Name) + cert, err := DecodeCert(certPEM.Bytes()) + if err != nil { + return err + } + + log.Trace(cert.Subject.CommonName) + + if secret.Annotations["revokedAt"] == "" { + indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "V", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) + } else if cert.NotAfter.Before(time.Now()) { + indexTxt += fmt.Sprintf("%s\t%s\t\t%s\t%s\t%s\n", "E", cert.NotAfter.Format(indexTxtDateFormat), fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) + } else { + indexTxt += fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", "R", cert.NotAfter.Format(indexTxtDateFormat), secret.Annotations["revokedAt"], fmt.Sprintf("%d", cert.SerialNumber), "unknown", "/CN="+secret.Labels["name"]) + } + } + + secretMetaData := metav1.ObjectMeta{Name: secretIndexTxt} + secretData := map[string][]byte{"index.txt": []byte(indexTxt)} + + if res, _ := openVPNPKI.secretCheckExists(secretIndexTxt); !res { + err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) + } else { + err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque) + } + case "filesystem": + indexTxtFromFile := GetIndexTxt(fRead(*IndexTxtPath)) + path := *EasyrsaDirPath+"/pki/issued/" + files := fReadDir(path) + for _, file := range files { + certData := fReadRaw(path+file.Name()) + certPEM := bytes.NewBuffer(certData) + log.Trace("indexTxtUpdate:" + file.Name()) + cert, err := DecodeCert(certPEM.Bytes()) + if err != nil { + return err + } + log.Trace(cert.Subject.CommonName) + + identity := string(cert.Subject.CommonName) + if cert.NotAfter.Before(time.Now()) { + if existIndex, ok := indexTxtFromFile[identity]; ok { + existIndex.Flag = "E" + indexTxtFromFile[identity] = existIndex + } else { + indexTxtFromFile[identity] = indexTxtLine { + Flag: "E", + ExpirationDate: cert.NotAfter.Format(indexTxtDateFormat), + SerialNumber: fmt.Sprintf("%X", cert.SerialNumber), + Filename: "unknown", + DistinguishedName: "/CN="+cert.Subject.CommonName, + Identity: "cert.Subject.CommonName", + } + } + } else { + indexTxtFromFile[identity] = indexTxtLine { + Flag: "V", + ExpirationDate: cert.NotAfter.Format(indexTxtDateFormat), + SerialNumber: fmt.Sprintf("%X", cert.SerialNumber), + Filename: "unknown", + DistinguishedName: "/CN="+cert.Subject.CommonName, + Identity: "cert.Subject.CommonName", + } + + } + } + + path = *EasyrsaDirPath+"/pki/revoked/certs_by_serial/" + filesRevoke := fReadDir(path) + for _, file := range filesRevoke { + if !strings.Contains(file.Name(), "-del-") { + certData := fReadRaw(path+file.Name()) + certPEM := bytes.NewBuffer(certData) + log.Trace("indexTxtUpdate:" + file.Name()) + cert, err := DecodeCert(certPEM.Bytes()) + if err != nil { + return err + } + log.Trace(cert.Subject.CommonName) + + identity := string(cert.Subject.CommonName) + if existIndex, ok := indexTxtFromFile[identity]; ok { + existIndex.Identity = "/CN="+cert.Subject.CommonName + if len(existIndex.RevocationDate)== 0 { + existIndex.RevocationDate = time.Now().Format(indexTxtDateFormat) + } + existIndex.Flag = "R" + indexTxtFromFile[identity] = existIndex + } + } + } + var body []string + for _, line := range indexTxtFromFile{ + body = append(body, fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", line.Flag, line.ExpirationDate, line.RevocationDate, line.SerialNumber, line.Filename, line.DistinguishedName)) + } + err = fWrite(*EasyrsaDirPath+"/pki/index.txt", strings.Join(body, "")) + if err != nil { + return err + } + } + return +} + + +func (openVPNPKI *OpenVPNPKI) GenPemCRL() (err error) { + + var revoked []*RevokedCert + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + return + } + + switch *StorageBackend { + case "kubernetes.secrets": + secrets, err := openVPNPKI.secretsGetByLabels("index.txt=,type=clientAuth") + if err != nil { + log.Errorf("error geting secret by label:%s", err.Error()) + } + + for _, secret := range secrets.Items { + if secret.Annotations["revokedAt"] != "" { + revokedAt, err := time.Parse(indexTxtDateFormat, secret.Annotations["revokedAt"]) + if err != nil { + log.Warning(err) + } + cert, err := DecodeCert(secret.Data[certFileName]) + revoked = append(revoked, &RevokedCert{RevokedTime: revokedAt, Cert: cert}) + } + } + crl, err := GenCRL(revoked, openVPNPKI.CACert, openVPNPKI.CAPrivKeyRSA) + if err != nil { + return err + } + secretMetaData := metav1.ObjectMeta{Name: secretCRL} + secretData := map[string][]byte{ + "crl.pem": crl.Bytes(), + } + + if res, _ := openVPNPKI.secretCheckExists(secretCRL); !res { + err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) + } else { + err = openVPNPKI.secretUpdate(secretMetaData, secretData, v1.SecretTypeOpaque) + } + case "filesystem": + path := *EasyrsaDirPath+"/pki/revoked/certs_by_serial/" + indexTxtFromFile := GetIndexTxt(fRead(*IndexTxtPath)) + filesRevoke := fReadDir(path) + + for _, file := range filesRevoke { + var identity string + var revokedAt time.Time + certData := fReadRaw(path+file.Name()) + certPEM := bytes.NewBuffer(certData) + log.Trace("indexTxtUpdate:" + file.Name()) + cert, err := DecodeCert(certPEM.Bytes()) + if err != nil { + log.Errorf("error decode revoked cert:%s", err.Error()) + } + if strings.Contains(file.Name(), "-del-"){ + trimName := strings.TrimSuffix(file.Name(), ".crt") + serialN := strings.Split(trimName, "-del-")[1] + identity = fmt.Sprintf("REVOKED-%s-%s",cert.Subject.CommonName, serialN) + } else { + identity = string(cert.Subject.CommonName) + } + if existIndex, ok := indexTxtFromFile[identity]; ok { + + revokedAt, err = time.Parse(indexTxtDateFormat, existIndex.RevocationDate) + + } else { + revokedAt = time.Now() + } + if err != nil { + return err + } + revoked = append(revoked, &RevokedCert{RevokedTime: revokedAt, Cert: cert}) + } + crl, _ := GenCRL(revoked, openVPNPKI.CACert, openVPNPKI.CAPrivKeyRSA) + + err = fWriteRaw(fmt.Sprintf("%s/pki/crl.pem", *EasyrsaDirPath), crl.Bytes(), 0600) + if err != nil { + log.Errorf("error write crl.pem:%s", err.Error()) + return err + } + } + return +} + + +func (openVPNPKI *OpenVPNPKI) initTaKeyAndDHParam() (err error) { + taKeyPath := fmt.Sprintf("%s/pki/ta.key", *EasyrsaDirPath) + dhparamPath := fmt.Sprintf("%s/pki/dh.pem", *EasyrsaDirPath) + switch *StorageBackend { + + case "kubernetes.secrets": + if res, _ := openVPNPKI.secretCheckExists(secretDHandTA); !res { + taKey, dhparam := openVPNPKI.generateTaKeyAndDHParam() + secretMetaData := metav1.ObjectMeta{Name: secretDHandTA} + + secretData := map[string][]byte{ + "ta.key": taKey, + "dh.pem": dhparam, + } + + err = openVPNPKI.secretCreate(secretMetaData, secretData, v1.SecretTypeOpaque) + if err != nil { + return err + } + openVPNPKI.TaKey = bytes.NewBuffer(taKey) + openVPNPKI.DhParam = bytes.NewBuffer(dhparam) + } else { + taKey := fReadRaw(taKeyPath) + dhparam := fReadRaw(dhparamPath) + openVPNPKI.TaKey = bytes.NewBuffer(taKey) + openVPNPKI.DhParam = bytes.NewBuffer(dhparam) + + } + + case "filesystem": + if res := fExist(taKeyPath); !res { + taKey, dhparam := openVPNPKI.generateTaKeyAndDHParam() + openVPNPKI.TaKey = bytes.NewBuffer(taKey) + openVPNPKI.DhParam = bytes.NewBuffer(dhparam) + } else{ + taKey := fReadRaw(taKeyPath) + dhparam := fReadRaw(dhparamPath) + openVPNPKI.TaKey = bytes.NewBuffer(taKey) + openVPNPKI.DhParam = bytes.NewBuffer(dhparam) + } + } + return +} + +func (openVPNPKI *OpenVPNPKI) generateTaKeyAndDHParam() (taKey []byte, dhparam []byte) { + taKeyPath := fmt.Sprintf("%s/pki/ta.key", *EasyrsaDirPath) + cmd := exec.Command("bash", "-c", fmt.Sprintf("/usr/sbin/openvpn --genkey secret %s", taKeyPath)) + stdout, err := cmd.CombinedOutput() + log.Info(fmt.Sprintf("/usr/sbin/openvpn --genkey secret %s: %s", taKeyPath, string(stdout))) + if err != nil { + return + } + taKey = fReadRaw(taKeyPath) + + dhparamPath := fmt.Sprintf("%s/pki//dh.pem", *EasyrsaDirPath) + cmd = exec.Command("bash", "-c", fmt.Sprintf("openssl dhparam -out %s 2048", dhparamPath)) + _, err = cmd.CombinedOutput() + if err != nil { + return + } + dhparam = fReadRaw(dhparamPath) + + return taKey, dhparam +} + + + +func (openVPNPKI *OpenVPNPKI) updateFiles() (err error) { + + caPath := fmt.Sprintf("%s/pki/ca.crt", *EasyrsaDirPath) + caKeyPath := fmt.Sprintf("%s/pki/private/ca.key", *EasyrsaDirPath) + + serverCAPath := fmt.Sprintf("%s/pki/issued/server.crt", *EasyrsaDirPath) + serverKeyPath := fmt.Sprintf("%s/pki/private/server.key", *EasyrsaDirPath) + + taKeyPath := fmt.Sprintf("%s/pki/ta.key", *EasyrsaDirPath) + dhparamPath := fmt.Sprintf("%s/pki/dh.pem", *EasyrsaDirPath) + + + if *StorageBackend == "kubernetes.secrets"{ + err = openVPNPKI.updateCRLOnDisk() + if err != nil { + return err + } + } + + + + if _, err := os.Stat(fmt.Sprintf("%s/pki/issued", *EasyrsaDirPath)); os.IsNotExist(err) { + err = os.MkdirAll(fmt.Sprintf("%s/pki/issued", *EasyrsaDirPath), 0755) + } + + if _, err := os.Stat(fmt.Sprintf("%s/pki/private", *EasyrsaDirPath)); os.IsNotExist(err) { + err = os.MkdirAll(fmt.Sprintf("%s/pki/private", *EasyrsaDirPath), 0755) + } + + if _, err := os.Stat(fmt.Sprintf("%s/pki/certs_by_serial", *EasyrsaDirPath)); os.IsNotExist(err) { + err = os.MkdirAll(fmt.Sprintf("%s/pki/certs_by_serial", *EasyrsaDirPath), 0755) + } + + if _, err := os.Stat(fmt.Sprintf("%s/pki/revoked/private_by_serial", *EasyrsaDirPath)); os.IsNotExist(err) { + err = os.MkdirAll(fmt.Sprintf("%s/pki/revoked/private_by_serial", *EasyrsaDirPath), 0755) + } + + if _, err := os.Stat(fmt.Sprintf("%s/pki/revoked/certs_by_serial", *EasyrsaDirPath)); os.IsNotExist(err) { + err = os.MkdirAll(fmt.Sprintf("%s/pki/revoked/certs_by_serial", *EasyrsaDirPath), 0755) + } + + + if !fExist(caPath){ + err = os.WriteFile(caPath, openVPNPKI.CACertPEM.Bytes(), 0600) + if err != nil { + log.Error(err) + } + err = os.WriteFile(caKeyPath, openVPNPKI.CAPrivKeyPEM.Bytes(), 0600) + if err != nil { + log.Error(err) + } + } + + if !fExist(serverCAPath){ + err = os.WriteFile(serverCAPath, openVPNPKI.ServerCertPEM.Bytes(), 0600) + if err != nil { + log.Error(err) + } + + err = os.WriteFile(serverKeyPath, openVPNPKI.ServerPrivKeyPEM.Bytes(), 0600) + if err != nil { + log.Error(err) + } + } + + if !fExist(taKeyPath) { + err = os.WriteFile(taKeyPath, openVPNPKI.TaKey.Bytes(), 0600) + if err != nil { + log.Error(err) + } + } + + if !fExist(dhparamPath) { + err = os.WriteFile(dhparamPath, openVPNPKI.DhParam.Bytes(), 0600) + if err != nil { + log.Error(err) + } + } + return +} + + + +func (openVPNPKI *OpenVPNPKI) CertificateRevoke(commonName string) (err error) { + + switch *StorageBackend { + case "kubernetes.secrets": + secret, _ := openVPNPKI.secretGetByLabels("name=" + commonName) + + if secret.Annotations["revokedAt"] != "" { + log.Warnf("user (%s) already revoked", commonName) + return + } + + secret.Annotations["revokedAt"] = time.Now().Format(indexTxtDateFormat) + + _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + return + } + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + return + } + + err = openVPNPKI.updateIndexTxtOnDisk() + if err != nil { + return + } + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + + err = openVPNPKI.updateCRLOnDisk() + case "filesystem": + + certPath := fmt.Sprintf("%s/pki/issued/%s.crt", *EasyrsaDirPath, commonName) + certKeyPath := fmt.Sprintf("%s/pki/private/%s.key", *EasyrsaDirPath, commonName) + + certData := fReadRaw(certPath) + cert, _ := DecodeCert(certData) + serialCrtPath := fmt.Sprintf("%s/pki/certs_by_serial/%X.pem", *EasyrsaDirPath, cert.SerialNumber) + + revokedCertPath := fmt.Sprintf("%s/pki/revoked/certs_by_serial/%X.crt", *EasyrsaDirPath, cert.SerialNumber) + revokedCertKeyPath := fmt.Sprintf("%s/pki/revoked/private_by_serial/%X.key", *EasyrsaDirPath, cert.SerialNumber) + + err = fMove(certPath, revokedCertPath) + if err != nil { + log.Errorf("fail user revoke: %s", err.Error()) + return + } + + err = fMove(certKeyPath, revokedCertKeyPath) + if err != nil { + log.Errorf("fail user revoke: %s", err.Error()) + return + } + + _ = fDelete(serialCrtPath) + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + return + } + + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + } + return +} + + +func (openVPNPKI *OpenVPNPKI) CertificateUnRevoke(commonName string) (err error) { + switch *StorageBackend { + + case "kubernetes.secrets": + secret, _ := openVPNPKI.secretGetByLabels("name=" + commonName) + if err != nil { + log.Error(err) + } + + secret.Annotations["revokedAt"] = "" + + _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + return + } + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + log.Errorf("fail user unrevoke: %s", err.Error()) + return + } + + err = openVPNPKI.updateIndexTxtOnDisk() + if err != nil { + log.Errorf("fail user unrevoke: %s", err.Error()) + return + } + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + + err = openVPNPKI.updateCRLOnDisk() + case "filesystem": + serialNumberInTxt := GetSerialNumberByUser(commonName) + + revokedCertPath := fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *EasyrsaDirPath, serialNumberInTxt) + revokedCertKeyPath := fmt.Sprintf("%s/pki/revoked/private_by_serial/%s.key", *EasyrsaDirPath, serialNumberInTxt) + + certPath := fmt.Sprintf("%s/pki/issued/%s.crt", *EasyrsaDirPath, commonName) + certKeyPath := fmt.Sprintf("%s/pki/private/%s.key", *EasyrsaDirPath, commonName) + + certData := fReadRaw(revokedCertPath) + cert, _ := DecodeCert(certData) + + serialCrtPath := fmt.Sprintf("%s/pki/certs_by_serial/%X.pem", *EasyrsaDirPath, cert.SerialNumber) + + + err = fMove(revokedCertPath, serialCrtPath) + if err != nil { + log.Errorf("fail user unrevoke: %s", err.Error()) + return + } + err = fMove(revokedCertKeyPath, certKeyPath) + if err != nil { + log.Errorf("fail user unrevoke: %s", err.Error()) + return + } + err = fCopy(serialCrtPath, certPath) + if err != nil { + log.Errorf("fail user unrevoke: %s", err.Error()) + return + } + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + log.Errorf("fail user unrevoke: %s", err.Error()) + return + } + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + } + return +} + + + +func (openVPNPKI *OpenVPNPKI) CertificateRotate(commonName string) (err error) { + + switch *StorageBackend { + + case "kubernetes.secrets": + secret, _ := openVPNPKI.secretGetByLabels("name=" + commonName) + if err != nil { + log.Error(err) + } + uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) + secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash + secret.Labels["name"] = "REVOKED" + commonName + secret.Labels["revokedForever"] = "true" + + _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + return + } + + err = openVPNPKI.BuildKeyPairClient(commonName) + if err != nil { + return + } + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + return + } + + err = openVPNPKI.updateIndexTxtOnDisk() + if err != nil { + return + } + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + + err = openVPNPKI.updateCRLOnDisk() + + case "filesystem": + serialNumberInTxt := GetSerialNumberByUser(commonName) + + revokedCertPath := fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *EasyrsaDirPath, serialNumberInTxt) + revokedCertKeyPath := fmt.Sprintf("%s/pki/revoked/private_by_serial/%s.key", *EasyrsaDirPath, serialNumberInTxt) + + indexTxtFromFile := GetIndexTxt(fRead(*IndexTxtPath)) + + certData := fReadRaw(revokedCertPath) + certPEM := bytes.NewBuffer(certData) + cert, _ := DecodeCert(certPEM.Bytes()) + + permanentRevokedCertPath := fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s-del-%s.crt", *EasyrsaDirPath, cert.Subject.CommonName, serialNumberInTxt) + permanentRevokedCertKeyPath := fmt.Sprintf("%s/pki/revoked/private_by_serial/%s-del-%s.key", *EasyrsaDirPath, cert.Subject.CommonName ,serialNumberInTxt) + + + identity := string(cert.Subject.CommonName) + + if existIndex, ok := indexTxtFromFile[identity]; ok { + existIndex.DistinguishedName = fmt.Sprintf("/CN=REVOKED-%s-%s", cert.Subject.CommonName, serialNumberInTxt) + indexTxtFromFile[identity] = existIndex + } + var body []string + for _, line := range indexTxtFromFile{ + body = append(body, fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", line.Flag, line.ExpirationDate, line.RevocationDate, line.SerialNumber, line.Filename, line.DistinguishedName)) + } + err = fWrite(*EasyrsaDirPath+"/pki/index.txt", strings.Join(body, "")) + if err != nil { + return err + } + + + + err = fMove(revokedCertPath, permanentRevokedCertPath) + if err != nil { + log.Errorf("fail user revoke: %s", err.Error()) + return + } + err = fMove(revokedCertKeyPath, permanentRevokedCertKeyPath) + if err != nil { + log.Errorf("fail user revoke: %s", err.Error()) + return + } + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + return + } + + err = openVPNPKI.BuildKeyPairClient(commonName) + if err != nil { + return + } + + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + } + return +} + +func (openVPNPKI *OpenVPNPKI) CertificateDelAfterRevoke(commonName string) (err error) { + + switch *StorageBackend { + case "kubernetes.secrets": + + secret, _ := openVPNPKI.secretGetByLabels("name=" + commonName) + if err != nil { + log.Error(err) + } + uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) + secret.Annotations["commonName"] = "REVOKED-" + commonName + "-" + uniqHash + secret.Labels["name"] = "REVOKED-" + commonName + "-" + uniqHash + secret.Labels["revokedForever"] = "true" + + _, err = openVPNPKI.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + return + } + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + return + } + + err = openVPNPKI.updateIndexTxtOnDisk() + if err != nil { + return + } + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + + err = openVPNPKI.updateCRLOnDisk() + + case "filesystem": + serialNumberInTxt := GetSerialNumberByUser(commonName) + // uniqHash := strings.Replace(uuid.New().String(), "-", "", -1) + + revokedCertPath := fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s.crt", *EasyrsaDirPath, serialNumberInTxt) + revokedCertKeyPath := fmt.Sprintf("%s/pki/revoked/private_by_serial/%s.key", *EasyrsaDirPath, serialNumberInTxt) + + indexTxtFromFile := GetIndexTxt(fRead(*IndexTxtPath)) + + certData := fReadRaw(revokedCertPath) + certPEM := bytes.NewBuffer(certData) + cert, _ := DecodeCert(certPEM.Bytes()) + + permanentRevokedCertPath := fmt.Sprintf("%s/pki/revoked/certs_by_serial/%s-del-%s.crt", *EasyrsaDirPath, cert.Subject.CommonName, serialNumberInTxt) + permanentRevokedCertKeyPath := fmt.Sprintf("%s/pki/revoked/private_by_serial/%s-del-%s.key", *EasyrsaDirPath, cert.Subject.CommonName, serialNumberInTxt) + + + identity := string(cert.Subject.CommonName) + + if existIndex, ok := indexTxtFromFile[identity]; ok { + existIndex.DistinguishedName = fmt.Sprintf("/CN=REVOKED-%s-%s", cert.Subject.CommonName, serialNumberInTxt) + + indexTxtFromFile[identity] = existIndex + } + var body []string + for _, line := range indexTxtFromFile{ + body = append(body, fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", line.Flag, line.ExpirationDate, line.RevocationDate, line.SerialNumber, line.Filename, line.DistinguishedName)) + } + err = fWrite(*EasyrsaDirPath+"/pki/index.txt", strings.Join(body, "")) + if err != nil { + return err + } + + + err = fMove(revokedCertPath, permanentRevokedCertPath) + if err != nil { + log.Errorf("fail user revoke: %s", err.Error()) + return + } + + err = fMove(revokedCertKeyPath, permanentRevokedCertKeyPath) + if err != nil { + log.Errorf("fail user revoke: %s", err.Error()) + return + } + + err = openVPNPKI.indexTxtUpdate() + if err != nil { + return + } + + err = openVPNPKI.GenPemCRL() + if err != nil { + log.Error(err) + } + } + return +} + +func GetIndexTxt(txt string) map[string]indexTxtLine { + myIndexTxt := make(map[string]indexTxtLine) + + + for _, v := range IndexTxtParser(txt) { + a := indexTxtLine{ + Flag: v.Flag, + ExpirationDate: v.ExpirationDate, + RevocationDate: v.RevocationDate, + SerialNumber: v.SerialNumber, + Filename: v.Filename, + DistinguishedName: v.DistinguishedName, + Identity: v.Identity} + myIndexTxt[v.Identity] = a + } + + return myIndexTxt +} \ No newline at end of file diff --git a/backend/utils.go b/backend/utils.go index 0462124..12c8298 100644 --- a/backend/utils.go +++ b/backend/utils.go @@ -5,12 +5,13 @@ import ( "compress/gzip" "context" "crypto/x509" + "database/sql" "encoding/pem" "errors" "fmt" log "github.com/sirupsen/logrus" "io" - "io/ioutil" + "io/fs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -24,7 +25,6 @@ import ( "strings" "time" "unicode/utf8" - "database/sql" ) var ( @@ -209,7 +209,7 @@ func getOvpnServerHostsFromKubeApi() ([]OpenvpnServer, error) { func getOvpnCaCertExpireDate() time.Time { caCertPath := *EasyrsaDirPath + "/pki/ca.crt" - caCert, err := ioutil.ReadFile(caCertPath) + caCert, err := os.ReadFile(caCertPath) if err != nil { log.Errorf("error read file %s: %s", caCertPath, err.Error()) } @@ -278,13 +278,27 @@ func fExist(path string) bool { } func fRead(path string) string { - content, err := ioutil.ReadFile(path) + content := fReadRaw(path) + return string(content) +} + +func fReadRaw(path string) []byte { + content, err := os.ReadFile(path) if err != nil { log.Warning(err) - return "" + return nil } - return string(content) + return content +} + +func fReadDir(path string) []fs.DirEntry { + files, err := os.ReadDir(path) + if err != nil { + log.Warning(err) + } + + return files } func fCreate(path string) error { @@ -301,7 +315,15 @@ func fCreate(path string) error { } func fWrite(path, content string) error { - err := ioutil.WriteFile(path, []byte(content), 0644) + err := fWriteRaw(path, []byte(content), 0644) + if err != nil { + log.Fatal(err) + } + return nil +} + +func fWriteRaw(path string, rawContent []byte, perm fs.FileMode) error { + err := os.WriteFile(path, []byte(rawContent), perm) if err != nil { log.Fatal(err) } @@ -395,7 +417,7 @@ func fDownload(path, url string, basicAuth bool) error { } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return err } @@ -572,4 +594,18 @@ func IsModuleEnabled(desiredModule string, listModules []string) bool { } } return false +} + +func GetSerialNumberByUser(username string) string { + var serialNumberInTxt string + usersFromIndexTxt := IndexTxtParser(fRead(*IndexTxtPath)) + for i := range usersFromIndexTxt { + if usersFromIndexTxt[i].DistinguishedName == "/CN="+username { + if usersFromIndexTxt[i].Flag == "R" { + serialNumberInTxt = usersFromIndexTxt[i].SerialNumber + break + } + } + } + return serialNumberInTxt } \ No newline at end of file diff --git a/main.go b/main.go index 3d7279b..5dd8867 100644 --- a/main.go +++ b/main.go @@ -50,9 +50,16 @@ func main() { ovpnAdmin := new(backend.OvpnAdmin) ovpnAdmin.OUser = new(ou.OpenvpnUser) + ovpnAdmin.PKI = new(backend.OpenVPNPKI) + err := ovpnAdmin.PKI.InitPKI() + if err != nil { + log.Error(err) + } + if *backend.StorageBackend == "kubernetes.secrets" { + // TODO: Check ovpnAdmin.KubeClient = new(backend.OpenVPNPKI) - err := ovpnAdmin.KubeClient.Run() + err := ovpnAdmin.KubeClient.KubeRun() if err != nil { log.Error(err) }