| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- package certs
- import (
- "context"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/x509"
- "encoding/pem"
- "fmt"
- "os"
- "path/filepath"
- "proxmox-router/internal/logging"
- "strings"
- "time"
- )
- func (cm *CertManager) renewCandidates(now time.Time) []LetsEncryptRequest {
- cm.mu.RLock()
- defer cm.mu.RUnlock()
- var requests []LetsEncryptRequest
- for _, cert := range cm.certs {
- if cert.Type == TypeACME && cert.ValidTo.Before(now.Add(30*24*time.Hour)) {
- req := LetsEncryptRequest{Name: cert.Name, Domain: cert.CommonName, Comment: cert.Comment, Staging: strings.Contains(cert.Issuer, "Staging")}
- if cert.ACMERequest != nil {
- req = *cert.ACMERequest
- }
- if req.Domain == "" {
- continue
- }
- req.renew = true
- requests = append(requests, req)
- }
- }
- return requests
- }
- func (cm *CertManager) RenewDue(ctx context.Context) {
- cm.renewMu.Lock()
- defer cm.renewMu.Unlock()
- for _, request := range cm.renewCandidates(time.Now()) {
- if ctx.Err() != nil {
- return
- }
- attemptCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
- _, err := cm.IssueLetsEncrypt(attemptCtx, request)
- cancel()
- cm.mu.Lock()
- if cert := cm.certs[strings.ToLower(request.Name)]; cert != nil {
- cert.LastRenewAttempt = time.Now()
- cert.RenewalError = ""
- if err != nil {
- cert.RenewalError = err.Error()
- }
- if saveErr := cm.saveLocked(); saveErr != nil {
- logging.Log("certs,error", "Failed to persist renewal result: %v", saveErr)
- }
- }
- cm.mu.Unlock()
- if err != nil {
- logging.Log("certs,error", "Certificate %s renewal failed: %v", request.Name, err)
- } else {
- logging.Log("certs,info", "Certificate %s renewed", request.Name)
- }
- }
- }
- func (cm *CertManager) RunRenewal(ctx context.Context) {
- // Allow the HTTP challenge listener to become available first.
- timer := time.NewTimer(5 * time.Minute)
- defer timer.Stop()
- for {
- select {
- case <-ctx.Done():
- return
- case <-timer.C:
- cm.RenewDue(ctx)
- timer.Reset(12 * time.Hour)
- }
- }
- }
- func (cm *CertManager) acmeAccountKey(staging bool) (*ecdsa.PrivateKey, error) {
- cm.accountMu.Lock()
- defer cm.accountMu.Unlock()
- name := "acme-account-production.key"
- if staging {
- name = "acme-account-staging.key"
- }
- path := filepath.Join(cm.dir, name)
- data, err := os.ReadFile(path)
- if err == nil {
- block, _ := pem.Decode(data)
- if block == nil {
- return nil, fmt.Errorf("invalid ACME account key")
- }
- return x509.ParseECPrivateKey(block.Bytes)
- }
- if !os.IsNotExist(err) {
- return nil, err
- }
- key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- return nil, err
- }
- if err = savePrivateKeyPEM(path, key); err != nil {
- return nil, err
- }
- return key, nil
- }
|