mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2024-11-18 10:29:43 +00:00
03881382a4
This PR add the `$NO_DNS_01` option (disabled by default) that removes the DNS ACME provider, and replaces the wildcard certificate by individual certificates obtained using the TLS ACME provider. This option allows an instance to work without having to manage access tokens for the DNS provider. On the flip side, this means that a certificate can be requested for each subdomains. To limit the risk of DOS, the existence of the user/org corresponding to a subdomain is checked before requesting a cert, however, this limitation is not enough for an forge with a high number of users/orgs. Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/290 Reviewed-by: Moritz Marquardt <momar@noreply.codeberg.org> Co-authored-by: Jean-Marie 'Histausse' Mineau <histausse@protonmail.com> Co-committed-by: Jean-Marie 'Histausse' Mineau <histausse@protonmail.com>
93 lines
3 KiB
Go
93 lines
3 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
|
|
}
|