ovpn-admin/backend/pki.go

1089 lines
36 KiB
Go

package backend
import (
"bytes"
"strings"
"context"
"errors"
"fmt"
"os"
"os/exec"
"sort"
"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
keys := make([]string, 0)
for k, _ := range indexTxtFromFile {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
body = append(body, fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", indexTxtFromFile[k].Flag, indexTxtFromFile[k].ExpirationDate, indexTxtFromFile[k].RevocationDate, indexTxtFromFile[k].SerialNumber, indexTxtFromFile[k].Filename, indexTxtFromFile[k].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
}