diff --git a/server/certificates/acme_client.go b/server/certificates/acme_client.go index f42fd8f..a05723a 100644 --- a/server/certificates/acme_client.go +++ b/server/certificates/acme_client.go @@ -70,6 +70,21 @@ func NewAcmeClient(cfg config.ACMEConfig, enableHTTPServer bool, challengeCache } } + acmeClientOrderLimit, err := equalizer.NewTokenBucket(25, 15*time.Minute) + if err != nil { + log.Fatal().Err(err).Msg("error creating token bucket") + } + + acmeClientRequestLimit, err := equalizer.NewTokenBucket(5, 1*time.Second) + if err != nil { + log.Fatal().Err(err).Msg("error creating token bucket") + } + + acmeClientFailLimit, err := equalizer.NewTokenBucket(5, 1*time.Hour) + if err != nil { + log.Fatal().Err(err).Msg("error creating token bucket") + } + return &AcmeClient{ legoClient: acmeClient, dnsChallengerLegoClient: mainDomainAcmeClient, @@ -82,11 +97,11 @@ func NewAcmeClient(cfg config.ACMEConfig, enableHTTPServer bool, challengeCache // 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), + acmeClientOrderLimit: acmeClientOrderLimit, // 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), + acmeClientRequestLimit: acmeClientRequestLimit, // rate limit is 5 / hour https://letsencrypt.org/docs/failed-validation-limit/ - acmeClientFailLimit: equalizer.NewTokenBucket(5, 1*time.Hour), + acmeClientFailLimit: acmeClientFailLimit, // checkUserLimit() use this to rate also per user acmeClientCertificateLimitPerUser: map[string]*equalizer.TokenBucket{}, }, nil diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go index 67219dd..268098e 100644 --- a/server/certificates/certificates.go +++ b/server/certificates/certificates.go @@ -161,10 +161,10 @@ func (c *AcmeClient) checkUserLimit(user string) error { userLimit, ok := c.acmeClientCertificateLimitPerUser[user] if !ok { // Each user can only add 10 new domains per day. - userLimit = equalizer.NewTokenBucket(10, time.Hour*24) + userLimit, _ = equalizer.NewTokenBucket(10, time.Hour*24) c.acmeClientCertificateLimitPerUser[user] = userLimit } - if !userLimit.Ask() { + if !userLimit.TryAcquire() { return fmt.Errorf("user '%s' error: %w", user, ErrUserRateLimitExceeded) } return nil @@ -243,16 +243,21 @@ func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew // request actual cert var res *certificate.Resource var err error + ctx := context.Background() if renew != nil && renew.CertURL != "" { if c.acmeUseRateLimits { - c.acmeClientRequestLimit.Take() + err = c.acmeClientFailLimit.Acquire(ctx) + if err != nil { + log.Error().Err(err).Msg("Failed to acquire fail limit") + } } log.Debug().Msgf("Renewing certificate for: %v", domains) res, err = acmeClient.Certificate.Renew(*renew, true, false, "") if err != nil { log.Error().Err(err).Msgf("Couldn't renew certificate for %v, trying to request a new one", domains) - if c.acmeUseRateLimits { - c.acmeClientFailLimit.Take() + err = c.acmeClientOrderLimit.Acquire(ctx) + if err != nil { + log.Error().Err(err).Msg("Failed to acquire order limit") } res = nil } @@ -265,8 +270,14 @@ func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew } if c.acmeUseRateLimits { - c.acmeClientOrderLimit.Take() - c.acmeClientRequestLimit.Take() + err = c.acmeClientOrderLimit.Acquire(ctx) + if err != nil { + log.Error().Err(err).Msg("Failed to acquire order limit") + } + err = c.acmeClientRequestLimit.Acquire(ctx) + if err != nil { + log.Error().Err(err).Msg("Failed to acquire request limit") + } } log.Debug().Msgf("Re-requesting new certificate for %v", domains) res, err = acmeClient.Certificate.Obtain(certificate.ObtainRequest{ @@ -275,7 +286,10 @@ func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew MustStaple: false, }) if c.acmeUseRateLimits && err != nil { - c.acmeClientFailLimit.Take() + err = c.acmeClientFailLimit.Acquire(ctx) + if err != nil { + log.Error().Err(err).Msg("Failed to acquire fail limit") + } } } if err != nil {