94 lines
3.0 KiB
Go
94 lines
3.0 KiB
Go
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/config"
|
|
"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(cfg config.ACMEConfig, enableHTTPServer bool, challengeCache cache.ICache) (*AcmeClient, error) {
|
|
acmeConfig, err := setupAcmeConfig(cfg)
|
|
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 cfg.DNSProvider == "" {
|
|
// using mock wildcard certs
|
|
mainDomainAcmeClient = nil
|
|
} else {
|
|
// use DNS-Challenge https://go-acme.github.io/lego/dns/
|
|
provider, err := dns.NewDNSChallengeProviderByName(cfg.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: cfg.UseRateLimits,
|
|
|
|
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 also per user
|
|
acmeClientCertificateLimitPerUser: map[string]*equalizer.TokenBucket{},
|
|
}, nil
|
|
}
|