move acmeClient creation into own file & struct

This commit is contained in:
6543 2023-02-11 00:32:52 +01:00
parent 1b6ea4b6e1
commit bee54de96f
No known key found for this signature in database
GPG Key ID: B8BE6D610E61C862
4 changed files with 172 additions and 112 deletions

View File

@ -3,7 +3,6 @@ package cmd
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@ -52,17 +51,6 @@ func Serve(ctx *cli.Context) error {
listeningAddress := fmt.Sprintf("%s:%s", ctx.String("host"), ctx.String("port")) listeningAddress := fmt.Sprintf("%s:%s", ctx.String("host"), ctx.String("port"))
enableHTTPServer := ctx.Bool("enable-http-server") enableHTTPServer := ctx.Bool("enable-http-server")
acmeAPI := ctx.String("acme-api-endpoint")
acmeMail := ctx.String("acme-email")
acmeUseRateLimits := ctx.Bool("acme-use-rate-limits")
acmeAcceptTerms := ctx.Bool("acme-accept-terms")
acmeEabKID := ctx.String("acme-eab-kid")
acmeEabHmac := ctx.String("acme-eab-hmac")
dnsProvider := ctx.String("dns-provider")
if (!acmeAcceptTerms || dnsProvider == "") && acmeAPI != "https://acme.mock.directory" {
return errors.New("you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory")
}
allowedCorsDomains := AllowedCorsDomains allowedCorsDomains := AllowedCorsDomains
if rawDomain != "" { if rawDomain != "" {
allowedCorsDomains = append(allowedCorsDomains, rawDomain) allowedCorsDomains = append(allowedCorsDomains, rawDomain)
@ -94,6 +82,15 @@ func Serve(ctx *cli.Context) error {
return fmt.Errorf("could not create new gitea client: %v", err) return fmt.Errorf("could not create new gitea client: %v", err)
} }
acmeClient, err := createAcmeClient(ctx, enableHTTPServer, challengeCache)
if err != nil {
return err
}
if err := certificates.SetupCertificates(mainDomainSuffix, acmeClient, certDB); err != nil {
return err
}
// Create handler based on settings // Create handler based on settings
httpsHandler := handler.Handler(mainDomainSuffix, rawDomain, httpsHandler := handler.Handler(mainDomainSuffix, rawDomain,
giteaClient, giteaClient,
@ -112,24 +109,14 @@ func Serve(ctx *cli.Context) error {
listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix, listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix,
giteaClient, giteaClient,
dnsProvider, acmeClient,
acmeUseRateLimits,
keyCache, challengeCache, dnsLookupCache, canonicalDomainCache, keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
certDB)) certDB))
acmeConfig, err := certificates.SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, acmeAcceptTerms)
if err != nil {
return err
}
if err := certificates.SetupCertificates(mainDomainSuffix, dnsProvider, acmeConfig, acmeUseRateLimits, enableHTTPServer, challengeCache, certDB); err != nil {
return err
}
interval := 12 * time.Hour interval := 12 * time.Hour
certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background()) certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background())
defer cancelCertMaintain() defer cancelCertMaintain()
go certificates.MaintainCertDB(certMaintainCtx, interval, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB) go certificates.MaintainCertDB(certMaintainCtx, interval, acmeClient, mainDomainSuffix, certDB)
if enableHTTPServer { if enableHTTPServer {
go func() { go func() {

View File

@ -1,14 +1,19 @@
package cmd package cmd
import ( import (
"errors"
"fmt" "fmt"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"codeberg.org/codeberg/pages/server/cache"
"codeberg.org/codeberg/pages/server/certificates"
"codeberg.org/codeberg/pages/server/database" "codeberg.org/codeberg/pages/server/database"
) )
var ErrAcmeMissConfig = errors.New("ACME client has wrong config")
func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) { func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) {
if ctx.String("db-type") != "" { if ctx.String("db-type") != "" {
log.Trace().Msg("use xorm mode") log.Trace().Msg("use xorm mode")
@ -43,3 +48,30 @@ The simplest way is, to use './pages certs migrate' and set environment var DB_T
return certDB, closeFn, nil return certDB, closeFn, nil
} }
func createAcmeClient(ctx *cli.Context, enableHTTPServer bool, challengeCache cache.SetGetKey) (*certificates.AcmeClient, error) {
acmeAPI := ctx.String("acme-api-endpoint")
acmeMail := ctx.String("acme-email")
acmeEabHmac := ctx.String("acme-eab-hmac")
acmeEabKID := ctx.String("acme-eab-kid")
acmeAcceptTerms := ctx.Bool("acme-accept-terms")
dnsProvider := ctx.String("dns-provider")
acmeUseRateLimits := ctx.Bool("acme-use-rate-limits")
// check config
if (!acmeAcceptTerms || dnsProvider == "") && acmeAPI != "https://acme.mock.directory" {
return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig)
}
return certificates.NewAcmeClient(
acmeAPI,
acmeMail,
acmeEabHmac,
acmeEabKID,
dnsProvider,
acmeAcceptTerms,
enableHTTPServer,
acmeUseRateLimits,
challengeCache,
)
}

View File

@ -0,0 +1,97 @@
package certificates
import (
"sync"
"time"
"codeberg.org/codeberg/pages/server/cache"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns"
"github.com/reugn/equalizer"
"github.com/rs/zerolog/log"
)
type AcmeClient struct {
legoClient *lego.Client
mainDomainLegoClient *lego.Client
dnsProvider string
obtainLocks sync.Map
acmeUseRateLimits bool
// limiter
acmeClientOrderLimit *equalizer.TokenBucket
acmeClientRequestLimit *equalizer.TokenBucket
acmeClientFailLimit *equalizer.TokenBucket
acmeClientCertificateLimitPerUser map[string]*equalizer.TokenBucket
}
func NewAcmeClient(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, dnsProvider string, acmeAcceptTerms, enableHTTPServer, acmeUseRateLimits bool, challengeCache cache.SetGetKey) (*AcmeClient, error) {
acmeConfig, err := SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, acmeAcceptTerms)
if err != nil {
return nil, err
}
acmeClient, err := lego.NewClient(acmeConfig)
if err != nil {
log.Fatal().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
} else {
err = acmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
if err != nil {
log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
}
if enableHTTPServer {
err = acmeClient.Challenge.SetHTTP01Provider(AcmeHTTPChallengeProvider{challengeCache})
if err != nil {
log.Error().Err(err).Msg("Can't create HTTP-01 provider")
}
}
}
mainDomainAcmeClient, err := lego.NewClient(acmeConfig)
if err != nil {
log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
} else {
if dnsProvider == "" {
// using mock server, don't use wildcard certs
err := mainDomainAcmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
if err != nil {
log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
}
} else {
provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
if err != nil {
log.Error().Err(err).Msg("Can't create DNS Challenge provider")
}
err = mainDomainAcmeClient.Challenge.SetDNS01Provider(provider)
if err != nil {
log.Error().Err(err).Msg("Can't create DNS-01 provider")
}
}
}
return &AcmeClient{
legoClient: acmeClient,
mainDomainLegoClient: mainDomainAcmeClient,
dnsProvider: dnsProvider,
acmeUseRateLimits: acmeUseRateLimits,
obtainLocks: sync.Map{},
// limiter
// rate limit is 300 / 3 hours, we want 200 / 2 hours but to refill more often, so that's 25 new domains every 15 minutes
// TODO: when this is used a lot, we probably have to think of a somewhat better solution?
acmeClientOrderLimit: equalizer.NewTokenBucket(25, 15*time.Minute),
// rate limit is 20 / second, we want 5 / second (especially as one cert takes at least two requests)
acmeClientRequestLimit: equalizer.NewTokenBucket(5, 1*time.Second),
// rate limit is 5 / hour https://letsencrypt.org/docs/failed-validation-limit/
acmeClientFailLimit: equalizer.NewTokenBucket(5, 1*time.Hour),
// checkUserLimit() use this to rate als per user
acmeClientCertificateLimitPerUser: map[string]*equalizer.TokenBucket{},
}, nil
}

View File

@ -13,7 +13,6 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/certcrypto"
@ -21,7 +20,6 @@ import (
"github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns"
"github.com/go-acme/lego/v4/registration" "github.com/go-acme/lego/v4/registration"
"github.com/reugn/equalizer" "github.com/reugn/equalizer"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -36,8 +34,7 @@ import (
// TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates. // TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates.
func TLSConfig(mainDomainSuffix string, func TLSConfig(mainDomainSuffix string,
giteaClient *gitea.Client, giteaClient *gitea.Client,
dnsProvider string, acmeClient *AcmeClient,
acmeUseRateLimits bool,
keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.SetGetKey, keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.SetGetKey,
certDB database.CertDB, certDB database.CertDB,
) *tls.Config { ) *tls.Config {
@ -100,7 +97,7 @@ func TLSConfig(mainDomainSuffix string,
var tlsCertificate *tls.Certificate var tlsCertificate *tls.Certificate
var err error var err error
if tlsCertificate, err = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); err != nil { if tlsCertificate, err = acmeClient.retrieveCertFromDB(sni, mainDomainSuffix, false, certDB); err != nil {
// request a new certificate // request a new certificate
if strings.EqualFold(sni, mainDomainSuffix) { if strings.EqualFold(sni, mainDomainSuffix) {
return nil, errors.New("won't request certificate for main domain, something really bad has happened") return nil, errors.New("won't request certificate for main domain, something really bad has happened")
@ -110,7 +107,7 @@ func TLSConfig(mainDomainSuffix string,
return nil, fmt.Errorf("won't request certificate for %q", sni) return nil, fmt.Errorf("won't request certificate for %q", sni)
} }
tlsCertificate, err = obtainCert(acmeClient, []string{sni}, nil, targetOwner, dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB) tlsCertificate, err = acmeClient.obtainCert(acmeClient.legoClient, []string{sni}, nil, targetOwner, false, mainDomainSuffix, certDB)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -141,34 +138,21 @@ func TLSConfig(mainDomainSuffix string,
} }
} }
func checkUserLimit(user string) error { var ErrUserRateLimitExceeded = errors.New("rate limit exceeded: 10 certificates per user per 24 hours")
userLimit, ok := acmeClientCertificateLimitPerUser[user]
func (c *AcmeClient) checkUserLimit(user string) error {
userLimit, ok := c.acmeClientCertificateLimitPerUser[user]
if !ok { if !ok {
// Each Codeberg user can only add 10 new domains per day. // Each user can only add 10 new domains per day.
userLimit = equalizer.NewTokenBucket(10, time.Hour*24) userLimit = equalizer.NewTokenBucket(10, time.Hour*24)
acmeClientCertificateLimitPerUser[user] = userLimit c.acmeClientCertificateLimitPerUser[user] = userLimit
} }
if !userLimit.Ask() { if !userLimit.Ask() {
return errors.New("rate limit exceeded: 10 certificates per user per 24 hours") return fmt.Errorf("user '%s' error: %w", user, ErrUserRateLimitExceeded)
} }
return nil return nil
} }
var (
acmeClient, mainDomainAcmeClient *lego.Client
acmeClientCertificateLimitPerUser = map[string]*equalizer.TokenBucket{}
)
// rate limit is 300 / 3 hours, we want 200 / 2 hours but to refill more often, so that's 25 new domains every 15 minutes
// TODO: when this is used a lot, we probably have to think of a somewhat better solution?
var acmeClientOrderLimit = equalizer.NewTokenBucket(25, 15*time.Minute)
// rate limit is 20 / second, we want 5 / second (especially as one cert takes at least two requests)
var acmeClientRequestLimit = equalizer.NewTokenBucket(5, 1*time.Second)
// rate limit is 5 / hour https://letsencrypt.org/docs/failed-validation-limit/
var acmeClientFailLimit = equalizer.NewTokenBucket(5, 1*time.Hour)
type AcmeTLSChallengeProvider struct { type AcmeTLSChallengeProvider struct {
challengeCache cache.SetGetKey challengeCache cache.SetGetKey
} }
@ -201,7 +185,7 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
return nil return nil
} }
func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (*tls.Certificate, error) { func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProvider bool, certDB database.CertDB) (*tls.Certificate, error) {
// parse certificate from database // parse certificate from database
res, err := certDB.Get(sni) res, err := certDB.Get(sni)
if err != nil { if err != nil {
@ -235,7 +219,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLi
// TODO: make a queue ? // TODO: make a queue ?
go (func() { go (func() {
res.CSR = nil // acme client doesn't like CSR to be set res.CSR = nil // acme client doesn't like CSR to be set
if _, err := obtainCert(acmeClient, []string{sni}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB); err != nil { if _, err := c.obtainCert(c.legoClient, []string{sni}, res, "", useDnsProvider, mainDomainSuffix, certDB); err != nil {
log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err) log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err)
} }
})() })()
@ -245,28 +229,26 @@ func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLi
return &tlsCertificate, nil return &tlsCertificate, nil
} }
var obtainLocks = sync.Map{} func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user string, useDnsProvider bool, mainDomainSuffix string, keyDatabase database.CertDB) (*tls.Certificate, error) {
func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider, mainDomainSuffix string, acmeUseRateLimits bool, keyDatabase database.CertDB) (*tls.Certificate, error) {
name := strings.TrimPrefix(domains[0], "*") name := strings.TrimPrefix(domains[0], "*")
if dnsProvider == "" && len(domains[0]) > 0 && domains[0][0] == '*' { if useDnsProvider && len(domains[0]) > 0 && domains[0][0] == '*' {
domains = domains[1:] domains = domains[1:]
} }
// lock to avoid simultaneous requests // lock to avoid simultaneous requests
_, working := obtainLocks.LoadOrStore(name, struct{}{}) _, working := c.obtainLocks.LoadOrStore(name, struct{}{})
if working { if working {
for working { for working {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
_, working = obtainLocks.Load(name) _, working = c.obtainLocks.Load(name)
} }
cert, err := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase) cert, err := c.retrieveCertFromDB(name, mainDomainSuffix, useDnsProvider, keyDatabase)
if err != nil { if err != nil {
return nil, fmt.Errorf("certificate failed in synchronous request: %w", err) return nil, fmt.Errorf("certificate failed in synchronous request: %w", err)
} }
return cert, nil return cert, nil
} }
defer obtainLocks.Delete(name) defer c.obtainLocks.Delete(name)
if acmeClient == nil { if acmeClient == nil {
return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase) return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase)
@ -276,29 +258,29 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
var res *certificate.Resource var res *certificate.Resource
var err error var err error
if renew != nil && renew.CertURL != "" { if renew != nil && renew.CertURL != "" {
if acmeUseRateLimits { if c.acmeUseRateLimits {
acmeClientRequestLimit.Take() c.acmeClientRequestLimit.Take()
} }
log.Debug().Msgf("Renewing certificate for: %v", domains) log.Debug().Msgf("Renewing certificate for: %v", domains)
res, err = acmeClient.Certificate.Renew(*renew, true, false, "") res, err = acmeClient.Certificate.Renew(*renew, true, false, "")
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Couldn't renew certificate for %v, trying to request a new one", domains) log.Error().Err(err).Msgf("Couldn't renew certificate for %v, trying to request a new one", domains)
if acmeUseRateLimits { if c.acmeUseRateLimits {
acmeClientFailLimit.Take() c.acmeClientFailLimit.Take()
} }
res = nil res = nil
} }
} }
if res == nil { if res == nil {
if user != "" { if user != "" {
if err := checkUserLimit(user); err != nil { if err := c.checkUserLimit(user); err != nil {
return nil, err return nil, err
} }
} }
if acmeUseRateLimits { if c.acmeUseRateLimits {
acmeClientOrderLimit.Take() c.acmeClientOrderLimit.Take()
acmeClientRequestLimit.Take() c.acmeClientRequestLimit.Take()
} }
log.Debug().Msgf("Re-requesting new certificate for %v", domains) log.Debug().Msgf("Re-requesting new certificate for %v", domains)
res, err = acmeClient.Certificate.Obtain(certificate.ObtainRequest{ res, err = acmeClient.Certificate.Obtain(certificate.ObtainRequest{
@ -306,8 +288,8 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
Bundle: true, Bundle: true,
MustStaple: false, MustStaple: false,
}) })
if acmeUseRateLimits && err != nil { if c.acmeUseRateLimits && err != nil {
acmeClientFailLimit.Take() c.acmeClientFailLimit.Take()
} }
} }
if err != nil { if err != nil {
@ -432,53 +414,15 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
return myAcmeConfig, nil return myAcmeConfig, nil
} }
func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error { func SetupCertificates(mainDomainSuffix string, acmeClient *AcmeClient, certDB database.CertDB) error {
// getting main cert before ACME account so that we can fail here without hitting rate limits // getting main cert before ACME account so that we can fail here without hitting rate limits
mainCertBytes, err := certDB.Get(mainDomainSuffix) mainCertBytes, err := certDB.Get(mainDomainSuffix)
if err != nil && !errors.Is(err, database.ErrNotFound) { if err != nil && !errors.Is(err, database.ErrNotFound) {
return fmt.Errorf("cert database is not working: %w", err) return fmt.Errorf("cert database is not working: %w", err)
} }
acmeClient, err = lego.NewClient(acmeConfig)
if err != nil {
log.Fatal().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
} else {
err = acmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
if err != nil {
log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
}
if enableHTTPServer {
err = acmeClient.Challenge.SetHTTP01Provider(AcmeHTTPChallengeProvider{challengeCache})
if err != nil {
log.Error().Err(err).Msg("Can't create HTTP-01 provider")
}
}
}
mainDomainAcmeClient, err = lego.NewClient(acmeConfig)
if err != nil {
log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
} else {
if dnsProvider == "" {
// using mock server, don't use wildcard certs
err := mainDomainAcmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
if err != nil {
log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
}
} else {
provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
if err != nil {
log.Error().Err(err).Msg("Can't create DNS Challenge provider")
}
err = mainDomainAcmeClient.Challenge.SetDNS01Provider(provider)
if err != nil {
log.Error().Err(err).Msg("Can't create DNS-01 provider")
}
}
}
if mainCertBytes == nil { if mainCertBytes == nil {
_, err = obtainCert(mainDomainAcmeClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, nil, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB) _, err = acmeClient.obtainCert(acmeClient.mainDomainLegoClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, nil, "", true, mainDomainSuffix, certDB)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Couldn't renew main domain certificate, continuing with mock certs only") log.Error().Err(err).Msg("Couldn't renew main domain certificate, continuing with mock certs only")
} }
@ -487,7 +431,7 @@ func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Co
return nil return nil
} }
func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) { func MaintainCertDB(ctx context.Context, interval time.Duration, acmeClient *AcmeClient, mainDomainSuffix string, certDB database.CertDB) {
for { for {
// delete expired certs that will be invalid until next clean up // delete expired certs that will be invalid until next clean up
threshold := time.Now().Add(interval) threshold := time.Now().Add(interval)
@ -533,7 +477,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
} else if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) { } else if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
// renew main certificate 30 days before it expires // renew main certificate 30 days before it expires
go (func() { go (func() {
_, err = obtainCert(mainDomainAcmeClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB) _, err = acmeClient.obtainCert(acmeClient.mainDomainLegoClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", true, mainDomainSuffix, certDB)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Couldn't renew certificate for main domain") log.Error().Err(err).Msg("Couldn't renew certificate for main domain")
} }