mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2024-11-18 10:29:43 +00:00
Move acmeClient creation into own file & struct (#179)
get rid of gobal vars and make make functions with less args :) tldr: collect funcs and create a own ACME client to manage that stuff Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/179
This commit is contained in:
parent
fd643d15f0
commit
5753f7136d
8 changed files with 323 additions and 237 deletions
10
cmd/flags.go
10
cmd/flags.go
|
@ -140,9 +140,15 @@ var (
|
||||||
EnvVars: []string{"ACME_EAB_HMAC"},
|
EnvVars: []string{"ACME_EAB_HMAC"},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "dns-provider",
|
Name: "dns-provider",
|
||||||
// TODO: Usage
|
Usage: "Use DNS-Challenge for main domain\n\nRead more at: https://go-acme.github.io/lego/dns/",
|
||||||
EnvVars: []string{"DNS_PROVIDER"},
|
EnvVars: []string{"DNS_PROVIDER"},
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "acme-account-config",
|
||||||
|
Usage: "json file of acme account",
|
||||||
|
Value: "acme-account.json",
|
||||||
|
EnvVars: []string{"ACME_ACCOUNT_CONFIG"},
|
||||||
|
},
|
||||||
}...)
|
}...)
|
||||||
)
|
)
|
||||||
|
|
35
cmd/main.go
35
cmd/main.go
|
@ -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.SetupMainDomainCertificates(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() {
|
||||||
|
|
34
cmd/setup.go
34
cmd/setup.go
|
@ -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) {
|
||||||
certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
|
certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -23,3 +28,32 @@ func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err e
|
||||||
|
|
||||||
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")
|
||||||
|
acmeAccountConf := ctx.String("acme-account-config")
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
acmeAccountConf,
|
||||||
|
acmeAPI,
|
||||||
|
acmeMail,
|
||||||
|
acmeEabHmac,
|
||||||
|
acmeEabKID,
|
||||||
|
dnsProvider,
|
||||||
|
acmeAcceptTerms,
|
||||||
|
enableHTTPServer,
|
||||||
|
acmeUseRateLimits,
|
||||||
|
challengeCache,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
95
server/certificates/acme_client.go
Normal file
95
server/certificates/acme_client.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package certificates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns"
|
||||||
|
"github.com/reugn/equalizer"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"codeberg.org/codeberg/pages/server/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AcmeClient struct {
|
||||||
|
legoClient *lego.Client
|
||||||
|
dnsChallengerLegoClient *lego.Client
|
||||||
|
|
||||||
|
obtainLocks sync.Map
|
||||||
|
|
||||||
|
acmeUseRateLimits bool
|
||||||
|
|
||||||
|
// limiter
|
||||||
|
acmeClientOrderLimit *equalizer.TokenBucket
|
||||||
|
acmeClientRequestLimit *equalizer.TokenBucket
|
||||||
|
acmeClientFailLimit *equalizer.TokenBucket
|
||||||
|
acmeClientCertificateLimitPerUser map[string]*equalizer.TokenBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAcmeClient(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, dnsProvider string, acmeAcceptTerms, enableHTTPServer, acmeUseRateLimits bool, challengeCache cache.SetGetKey) (*AcmeClient, error) {
|
||||||
|
acmeConfig, err := setupAcmeConfig(acmeAccountConf, 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 {
|
||||||
|
// use DNS-Challenge https://go-acme.github.io/lego/dns/
|
||||||
|
provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can not create DNS Challenge provider: %w", err)
|
||||||
|
}
|
||||||
|
if err := mainDomainAcmeClient.Challenge.SetDNS01Provider(provider); err != nil {
|
||||||
|
return nil, fmt.Errorf("can not create DNS-01 provider: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AcmeClient{
|
||||||
|
legoClient: acmeClient,
|
||||||
|
dnsChallengerLegoClient: mainDomainAcmeClient,
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
100
server/certificates/acme_config.go
Normal file
100
server/certificates/acme_config.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package certificates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupAcmeConfig(configFile, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
|
||||||
|
var myAcmeAccount AcmeAccount
|
||||||
|
var myAcmeConfig *lego.Config
|
||||||
|
|
||||||
|
if account, err := os.ReadFile(configFile); err == nil {
|
||||||
|
log.Info().Msgf("found existing acme account config file '%s'", configFile)
|
||||||
|
if err := json.Unmarshal(account, &myAcmeAccount); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
myAcmeAccount.Key, err = certcrypto.ParsePEMPrivateKey([]byte(myAcmeAccount.KeyPEM))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
myAcmeConfig = lego.NewConfig(&myAcmeAccount)
|
||||||
|
myAcmeConfig.CADirURL = acmeAPI
|
||||||
|
myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
|
||||||
|
// Validate Config
|
||||||
|
_, err := lego.NewClient(myAcmeConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Info().Err(err).Msg("config validation failed, you might just delete the config file and let it recreate")
|
||||||
|
return nil, fmt.Errorf("acme config validation failed: %w", err)
|
||||||
|
}
|
||||||
|
return myAcmeConfig, nil
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("no existing acme account config found, try to create a new one")
|
||||||
|
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
myAcmeAccount = AcmeAccount{
|
||||||
|
Email: acmeMail,
|
||||||
|
Key: privateKey,
|
||||||
|
KeyPEM: string(certcrypto.PEMEncode(privateKey)),
|
||||||
|
}
|
||||||
|
myAcmeConfig = lego.NewConfig(&myAcmeAccount)
|
||||||
|
myAcmeConfig.CADirURL = acmeAPI
|
||||||
|
myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
tempClient, err := lego.NewClient(myAcmeConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
|
||||||
|
} else {
|
||||||
|
// accept terms & log in to EAB
|
||||||
|
if acmeEabKID == "" || acmeEabHmac == "" {
|
||||||
|
reg, err := tempClient.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: acmeAcceptTerms})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
|
||||||
|
} else {
|
||||||
|
myAcmeAccount.Registration = reg
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reg, err := tempClient.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||||
|
TermsOfServiceAgreed: acmeAcceptTerms,
|
||||||
|
Kid: acmeEabKID,
|
||||||
|
HmacEncoded: acmeEabHmac,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
|
||||||
|
} else {
|
||||||
|
myAcmeAccount.Registration = reg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if myAcmeAccount.Registration != nil {
|
||||||
|
acmeAccountJSON, err := json.Marshal(myAcmeAccount)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("json.Marshalfailed, waiting for manual restart to avoid rate limits")
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
log.Info().Msgf("new acme account created. write to config file '%s'", configFile)
|
||||||
|
err = os.WriteFile(configFile, acmeAccountJSON, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("os.WriteFile failed, waiting for manual restart to avoid rate limits")
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return myAcmeConfig, nil
|
||||||
|
}
|
41
server/certificates/cached_challengers.go
Normal file
41
server/certificates/cached_challengers.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package certificates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
|
||||||
|
"codeberg.org/codeberg/pages/server/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AcmeTLSChallengeProvider struct {
|
||||||
|
challengeCache cache.SetGetKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure AcmeTLSChallengeProvider match Provider interface
|
||||||
|
var _ challenge.Provider = AcmeTLSChallengeProvider{}
|
||||||
|
|
||||||
|
func (a AcmeTLSChallengeProvider) Present(domain, _, keyAuth string) error {
|
||||||
|
return a.challengeCache.Set(domain, keyAuth, 1*time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AcmeTLSChallengeProvider) CleanUp(domain, _, _ string) error {
|
||||||
|
a.challengeCache.Remove(domain)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AcmeHTTPChallengeProvider struct {
|
||||||
|
challengeCache cache.SetGetKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure AcmeHTTPChallengeProvider match Provider interface
|
||||||
|
var _ challenge.Provider = AcmeHTTPChallengeProvider{}
|
||||||
|
|
||||||
|
func (a AcmeHTTPChallengeProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
return a.challengeCache.Set(domain+"/"+token, keyAuth, 1*time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
|
||||||
|
a.challengeCache.Remove(domain + "/" + token)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,27 +2,18 @@ package certificates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"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/reugn/equalizer"
|
"github.com/reugn/equalizer"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
@ -33,11 +24,12 @@ import (
|
||||||
"codeberg.org/codeberg/pages/server/upstream"
|
"codeberg.org/codeberg/pages/server/upstream"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrUserRateLimitExceeded = errors.New("rate limit exceeded: 10 certificates per user per 24 hours")
|
||||||
|
|
||||||
// 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 +92,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 +102,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,67 +133,20 @@ func TLSConfig(mainDomainSuffix string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkUserLimit(user string) error {
|
func (c *AcmeClient) checkUserLimit(user string) error {
|
||||||
userLimit, ok := acmeClientCertificateLimitPerUser[user]
|
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 (
|
func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProvider bool, certDB database.CertDB) (*tls.Certificate, error) {
|
||||||
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 {
|
|
||||||
challengeCache cache.SetGetKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure AcmeTLSChallengeProvider match Provider interface
|
|
||||||
var _ challenge.Provider = AcmeTLSChallengeProvider{}
|
|
||||||
|
|
||||||
func (a AcmeTLSChallengeProvider) Present(domain, _, keyAuth string) error {
|
|
||||||
return a.challengeCache.Set(domain, keyAuth, 1*time.Hour)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AcmeTLSChallengeProvider) CleanUp(domain, _, _ string) error {
|
|
||||||
a.challengeCache.Remove(domain)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AcmeHTTPChallengeProvider struct {
|
|
||||||
challengeCache cache.SetGetKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure AcmeHTTPChallengeProvider match Provider interface
|
|
||||||
var _ challenge.Provider = AcmeHTTPChallengeProvider{}
|
|
||||||
|
|
||||||
func (a AcmeHTTPChallengeProvider) Present(domain, token, keyAuth string) error {
|
|
||||||
return a.challengeCache.Set(domain+"/"+token, keyAuth, 1*time.Hour)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
|
|
||||||
a.challengeCache.Remove(domain + "/" + token)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits 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 +180,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 +190,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 +219,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 +249,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 {
|
||||||
|
@ -349,136 +292,15 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
|
||||||
return &tlsCertificate, nil
|
return &tlsCertificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
|
func SetupMainDomainCertificates(mainDomainSuffix string, acmeClient *AcmeClient, certDB database.CertDB) error {
|
||||||
// TODO: make it a config flag
|
|
||||||
const configFile = "acme-account.json"
|
|
||||||
var myAcmeAccount AcmeAccount
|
|
||||||
var myAcmeConfig *lego.Config
|
|
||||||
|
|
||||||
if account, err := os.ReadFile(configFile); err == nil {
|
|
||||||
if err := json.Unmarshal(account, &myAcmeAccount); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
myAcmeAccount.Key, err = certcrypto.ParsePEMPrivateKey([]byte(myAcmeAccount.KeyPEM))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
myAcmeConfig = lego.NewConfig(&myAcmeAccount)
|
|
||||||
myAcmeConfig.CADirURL = acmeAPI
|
|
||||||
myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
|
|
||||||
|
|
||||||
// Validate Config
|
|
||||||
_, err := lego.NewClient(myAcmeConfig)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: should we fail hard instead?
|
|
||||||
log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
|
|
||||||
}
|
|
||||||
return myAcmeConfig, nil
|
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
myAcmeAccount = AcmeAccount{
|
|
||||||
Email: acmeMail,
|
|
||||||
Key: privateKey,
|
|
||||||
KeyPEM: string(certcrypto.PEMEncode(privateKey)),
|
|
||||||
}
|
|
||||||
myAcmeConfig = lego.NewConfig(&myAcmeAccount)
|
|
||||||
myAcmeConfig.CADirURL = acmeAPI
|
|
||||||
myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
|
|
||||||
tempClient, err := lego.NewClient(myAcmeConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
|
|
||||||
} else {
|
|
||||||
// accept terms & log in to EAB
|
|
||||||
if acmeEabKID == "" || acmeEabHmac == "" {
|
|
||||||
reg, err := tempClient.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: acmeAcceptTerms})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
|
|
||||||
} else {
|
|
||||||
myAcmeAccount.Registration = reg
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reg, err := tempClient.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
|
||||||
TermsOfServiceAgreed: acmeAcceptTerms,
|
|
||||||
Kid: acmeEabKID,
|
|
||||||
HmacEncoded: acmeEabHmac,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
|
|
||||||
} else {
|
|
||||||
myAcmeAccount.Registration = reg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if myAcmeAccount.Registration != nil {
|
|
||||||
acmeAccountJSON, err := json.Marshal(myAcmeAccount)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("json.Marshalfailed, waiting for manual restart to avoid rate limits")
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
err = os.WriteFile(configFile, acmeAccountJSON, 0o600)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("os.WriteFile failed, waiting for manual restart to avoid rate limits")
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return myAcmeConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, 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.dnsChallengerLegoClient, []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 +309,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)
|
||||||
|
@ -525,7 +347,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.dnsChallengerLegoClient, []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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@ package certificates
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"codeberg.org/codeberg/pages/server/database"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"codeberg.org/codeberg/pages/server/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMockCert(t *testing.T) {
|
func TestMockCert(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue