1083 lines
No EOL
36 KiB
Go
1083 lines
No EOL
36 KiB
Go
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
|
|
} |