267 lines
8.1 KiB
Go
267 lines
8.1 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.nevets.tech/Keys/CertManager/internal"
|
|
"github.com/go-git/go-billy/v5/memfs"
|
|
"github.com/go-git/go-git/v5/storage/memory"
|
|
)
|
|
|
|
var mgr *internal.ACMEManager
|
|
|
|
func RenewCertCmd(domain string, noPush bool, certmanMode internal.CertManMode) error {
|
|
err := internal.LoadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = internal.LoadDomainConfigs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch internal.Config().GetString("App.mode") {
|
|
case "server":
|
|
mgr, err = internal.NewACMEManager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = renewCerts(domain, noPush, certmanMode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ReloadDaemonCmd()
|
|
case "client":
|
|
return pullCerts(domain, certmanMode)
|
|
default:
|
|
return fmt.Errorf("invalid operating mode %s", internal.Config().GetString("App.mode"))
|
|
}
|
|
}
|
|
|
|
func renewCerts(domain string, noPush bool, certmanMode internal.CertManMode) error {
|
|
_, err := mgr.RenewForDomain(domain)
|
|
if err != nil {
|
|
// if no existing cert, obtain instead
|
|
_, err = mgr.ObtainForDomain(domain)
|
|
if err != nil {
|
|
return fmt.Errorf("error obtaining domain certificates for domain %s: %v", domain, err)
|
|
}
|
|
}
|
|
|
|
domainConfig, exists := internal.DomainStore().Get(domain)
|
|
if !exists {
|
|
return fmt.Errorf("domain %s does not exist", domain)
|
|
}
|
|
|
|
domainConfig.Set("Internal.last_issued", time.Now().UTC().Unix())
|
|
err = internal.WriteDomainConfig(domainConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("error saving domain config %s: %v", domain, err)
|
|
}
|
|
|
|
err = internal.EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.CertsRoot, domain, domain+".crt"), filepath.Join(mgr.CertsRoot, domain, domain+".crt.crpt"), nil)
|
|
if err != nil {
|
|
return fmt.Errorf("error encrypting domain cert for domain %s: %v", domain, err)
|
|
}
|
|
err = internal.EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.CertsRoot, domain, domain+".key"), filepath.Join(mgr.CertsRoot, domain, domain+".key.crpt"), nil)
|
|
if err != nil {
|
|
return fmt.Errorf("error encrypting domain key for domain %s: %v", domain, err)
|
|
}
|
|
|
|
if !noPush {
|
|
giteaClient := internal.CreateGiteaClient()
|
|
if giteaClient == nil {
|
|
return fmt.Errorf("error creating gitea client for domain %s: %v", domain, err)
|
|
}
|
|
gitWorkspace := &internal.GitWorkspace{
|
|
Storage: memory.NewStorage(),
|
|
FS: memfs.New(),
|
|
}
|
|
|
|
var repoUrl string
|
|
if !domainConfig.GetBool("Internal.repo_exists") {
|
|
repoUrl = internal.CreateGiteaRepo(domain, giteaClient)
|
|
if repoUrl == "" {
|
|
return fmt.Errorf("error creating Gitea repo for domain %s", domain)
|
|
}
|
|
domainConfig.Set("Internal.repo_exists", true)
|
|
err = internal.WriteDomainConfig(domainConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("error saving domain config %s: %v", domain, err)
|
|
}
|
|
|
|
err = internal.InitRepo(repoUrl, gitWorkspace)
|
|
if err != nil {
|
|
return fmt.Errorf("error initializing repo for domain %s: %v", domain, err)
|
|
}
|
|
} else {
|
|
repoUrl = internal.Config().GetString("Git.server") + "/" + internal.Config().GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
|
err = internal.CloneRepo(repoUrl, gitWorkspace, certmanMode)
|
|
if err != nil {
|
|
return fmt.Errorf("error cloning repo for domain %s: %v", domain, err)
|
|
}
|
|
}
|
|
|
|
err = internal.AddAndPushCerts(domain, gitWorkspace)
|
|
if err != nil {
|
|
return fmt.Errorf("error pushing certificates for domain %s: %v", domain, err)
|
|
}
|
|
fmt.Printf("Successfully pushed certificates for domain %s\n", domain)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func pullCerts(domain string, certmanMode internal.CertManMode) error {
|
|
gitWorkspace := &internal.GitWorkspace{
|
|
Storage: memory.NewStorage(),
|
|
FS: memfs.New(),
|
|
}
|
|
|
|
domainConfig, exists := internal.DomainStore().Get(domain)
|
|
if !exists {
|
|
return fmt.Errorf("domain %s does not exist", domain)
|
|
}
|
|
|
|
// Ex: https://git.example.com/Org/Repo-suffix.git
|
|
// Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?)
|
|
repoUrl := internal.Config().GetString("Git.server") + "/" + internal.Config().GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
|
err := internal.CloneRepo(repoUrl, gitWorkspace, certmanMode)
|
|
if err != nil {
|
|
return fmt.Errorf("Error cloning domain repo %s: %v\n", domain, err)
|
|
}
|
|
|
|
certsDir, err := internal.DomainCertsDirWConf(domain, domainConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("Error getting certificates dir for domain %s: %v\n", domain, err)
|
|
}
|
|
|
|
// Get files in repo
|
|
fileInfos, err := gitWorkspace.FS.ReadDir("/")
|
|
if err != nil {
|
|
return fmt.Errorf("Error reading directory in memFS on domain %s: %v\n", domain, err)
|
|
}
|
|
// Iterate over files, filtering by .crpt (encrypted) files in case other files were accidentally added
|
|
for _, fileInfo := range fileInfos {
|
|
if strings.HasSuffix(fileInfo.Name(), ".crpt") {
|
|
filename, _ := strings.CutSuffix(fileInfo.Name(), ".crpt")
|
|
file, err := gitWorkspace.FS.Open(fileInfo.Name())
|
|
if err != nil {
|
|
fmt.Printf("Error opening file in memFS on domain %s: %v\n", domain, err)
|
|
continue
|
|
}
|
|
fileBytes, err := io.ReadAll(file)
|
|
if err != nil {
|
|
fmt.Printf("Error reading file in memFS on domain %s: %v\n", domain, err)
|
|
file.Close()
|
|
continue
|
|
}
|
|
err = file.Close()
|
|
if err != nil {
|
|
fmt.Printf("Error closing file on domain %s: %v\n", domain, err)
|
|
continue
|
|
}
|
|
|
|
err = internal.DecryptFileFromBytes(domainConfig.GetString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil)
|
|
if err != nil {
|
|
fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domain, err)
|
|
continue
|
|
}
|
|
|
|
headRef, err := gitWorkspace.Repo.Head()
|
|
if err != nil {
|
|
fmt.Printf("Error getting head reference for domain %s: %v\n", domain, err)
|
|
continue
|
|
}
|
|
|
|
err = internal.WriteCommitHash(headRef.Hash().String(), domainConfig)
|
|
if err != nil {
|
|
fmt.Printf("Error writing commit hash: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
certLinks := domainConfig.GetStringSlice("Certificates.cert_symlinks")
|
|
for _, certLink := range certLinks {
|
|
if certLink == "" {
|
|
continue
|
|
}
|
|
linkInfo, err := os.Stat(certLink)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
fmt.Printf("Error stating cert link %s: %v\n", certLink, err)
|
|
continue
|
|
}
|
|
}
|
|
if linkInfo.IsDir() {
|
|
certLink = filepath.Join(certLink, domain+".crt")
|
|
}
|
|
|
|
err = os.Link(filepath.Join(certsDir, domain+".crt"), certLink)
|
|
if err != nil {
|
|
fmt.Printf("Error linking cert %s to %s: %v\n", certLink, domain, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
keyLinks := domainConfig.GetStringSlice("Certificates.key_symlinks")
|
|
for _, keyLink := range keyLinks {
|
|
if keyLink == "" {
|
|
continue
|
|
}
|
|
linkInfo, err := os.Stat(keyLink)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
fmt.Printf("Error stating key link %s: %v\n", keyLink, err)
|
|
continue
|
|
}
|
|
}
|
|
if linkInfo.IsDir() {
|
|
keyLink = filepath.Join(keyLink, domain+".crt")
|
|
}
|
|
|
|
err = os.Link(filepath.Join(certsDir, domain+".crt"), keyLink)
|
|
if err != nil {
|
|
fmt.Printf("Error linking cert %s to %s: %v\n", keyLink, domain, err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func UpdateLinksCmd(domain string) error {
|
|
domainConfig, exists := internal.DomainStore().Get(domain)
|
|
if !exists {
|
|
return fmt.Errorf("domain %s does not exist", domain)
|
|
}
|
|
|
|
certsDir, err := internal.DomainCertsDirWConf(domain, domainConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting certificates dir for domain %s: %v", domain, err)
|
|
}
|
|
|
|
certLinks := domainConfig.GetStringSlice("Certificates.cert_symlinks")
|
|
for _, certLink := range certLinks {
|
|
err = internal.LinkFile(filepath.Join(certsDir, domain+".crt"), certLink, domain, ".crt")
|
|
if err != nil {
|
|
fmt.Printf("Error linking cert %s to %s: %v", certLink, domain, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
keyLinks := domainConfig.GetStringSlice("Certificates.key_symlinks")
|
|
for _, keyLink := range keyLinks {
|
|
err = internal.LinkFile(filepath.Join(certsDir, domain+".crt"), keyLink, domain, ".key")
|
|
if err != nil {
|
|
fmt.Printf("Error linking cert %s to %s: %v", keyLink, domain, err)
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|