mirror of
https://github.com/SSLMate/certspotter.git
synced 2025-12-17 04:24:19 +01:00
Add optional rate limiting of log queries
If a log operator publishes a simple rate limit for a log, we can use that information to avoid sending requests to the log that we know will fail. This will improve throughput as we won't be wasting time backing off from failed requests.
This commit is contained in:
5
go.mod
5
go.mod
@@ -8,6 +8,9 @@ require (
|
|||||||
golang.org/x/sync v0.15.0
|
golang.org/x/sync v0.15.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.26.0 // indirect
|
require (
|
||||||
|
golang.org/x/text v0.26.0 // indirect
|
||||||
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
retract v0.19.0 // Contains serious bugs.
|
retract v0.19.0 // Contains serious bugs.
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -6,3 +6,5 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
|||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
|
|||||||
@@ -44,8 +44,9 @@ type Log struct {
|
|||||||
} `json:"temporal_interval,omitzero"`
|
} `json:"temporal_interval,omitzero"`
|
||||||
|
|
||||||
// certspotter-specific extensions
|
// certspotter-specific extensions
|
||||||
CertspotterDownloadSize int `json:"certspotter_download_size,omitzero"`
|
CertspotterDownloadSize int `json:"certspotter_download_size,omitzero"`
|
||||||
CertspotterDownloadJobs int `json:"certspotter_download_jobs,omitzero"`
|
CertspotterDownloadJobs int `json:"certspotter_download_jobs,omitzero"`
|
||||||
|
CertspotterDownloadQPS float64 `json:"certspotter_download_qps,omitzero"`
|
||||||
|
|
||||||
// TODO: add previous_operators
|
// TODO: add previous_operators
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
"log"
|
"log"
|
||||||
mathrand "math/rand/v2"
|
mathrand "math/rand/v2"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -44,13 +45,24 @@ func downloadJobSize(ctlog *loglist.Log) uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downloadWorkers(ctlog *loglist.Log) int {
|
func downloadWorkers(ctlog *loglist.Log) int {
|
||||||
if ctlog.CertspotterDownloadJobs != 0 {
|
if ctlog.CertspotterDownloadQPS != 0 {
|
||||||
|
// parallelism is effectively governed by the rate limit so for now just hard code a number here
|
||||||
|
return 10
|
||||||
|
} else if ctlog.CertspotterDownloadJobs != 0 {
|
||||||
return ctlog.CertspotterDownloadJobs
|
return ctlog.CertspotterDownloadJobs
|
||||||
} else {
|
} else {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downloadRateLimit(ctlog *loglist.Log) rate.Limit {
|
||||||
|
if ctlog.CertspotterDownloadQPS != 0 {
|
||||||
|
return rate.Limit(ctlog.CertspotterDownloadQPS)
|
||||||
|
} else {
|
||||||
|
return rate.Inf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type verifyEntriesError struct {
|
type verifyEntriesError struct {
|
||||||
sth *cttypes.SignedTreeHead
|
sth *cttypes.SignedTreeHead
|
||||||
entriesRootHash merkletree.Hash
|
entriesRootHash merkletree.Hash
|
||||||
@@ -113,10 +125,14 @@ type logClient struct {
|
|||||||
config *Config
|
config *Config
|
||||||
log *loglist.Log
|
log *loglist.Log
|
||||||
client ctclient.Log
|
client ctclient.Log
|
||||||
|
lim *rate.Limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *logClient) GetSTH(ctx context.Context) (sth *cttypes.SignedTreeHead, url string, err error) {
|
func (client *logClient) GetSTH(ctx context.Context) (sth *cttypes.SignedTreeHead, url string, err error) {
|
||||||
err = withRetry(ctx, client.config, client.log, -1, func() error {
|
err = withRetry(ctx, client.config, client.log, -1, func() error {
|
||||||
|
if err := client.lim.Wait(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
sth, url, err = getAuthenticSTH(ctx, client.log, client.client)
|
sth, url, err = getAuthenticSTH(ctx, client.log, client.client)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -124,6 +140,9 @@ func (client *logClient) GetSTH(ctx context.Context) (sth *cttypes.SignedTreeHea
|
|||||||
}
|
}
|
||||||
func (client *logClient) GetRoots(ctx context.Context) (roots [][]byte, err error) {
|
func (client *logClient) GetRoots(ctx context.Context) (roots [][]byte, err error) {
|
||||||
err = withRetry(ctx, client.config, client.log, -1, func() error {
|
err = withRetry(ctx, client.config, client.log, -1, func() error {
|
||||||
|
if err := client.lim.Wait(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
roots, err = client.client.GetRoots(ctx)
|
roots, err = client.client.GetRoots(ctx)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -131,6 +150,9 @@ func (client *logClient) GetRoots(ctx context.Context) (roots [][]byte, err erro
|
|||||||
}
|
}
|
||||||
func (client *logClient) GetEntries(ctx context.Context, startInclusive, endInclusive uint64) (entries []ctclient.Entry, err error) {
|
func (client *logClient) GetEntries(ctx context.Context, startInclusive, endInclusive uint64) (entries []ctclient.Entry, err error) {
|
||||||
err = withRetry(ctx, client.config, client.log, -1, func() error {
|
err = withRetry(ctx, client.config, client.log, -1, func() error {
|
||||||
|
if err := client.lim.Wait(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
entries, err = client.client.GetEntries(ctx, startInclusive, endInclusive)
|
entries, err = client.client.GetEntries(ctx, startInclusive, endInclusive)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -138,6 +160,9 @@ func (client *logClient) GetEntries(ctx context.Context, startInclusive, endIncl
|
|||||||
}
|
}
|
||||||
func (client *logClient) ReconstructTree(ctx context.Context, sth *cttypes.SignedTreeHead) (tree *merkletree.CollapsedTree, err error) {
|
func (client *logClient) ReconstructTree(ctx context.Context, sth *cttypes.SignedTreeHead) (tree *merkletree.CollapsedTree, err error) {
|
||||||
err = withRetry(ctx, client.config, client.log, -1, func() error {
|
err = withRetry(ctx, client.config, client.log, -1, func() error {
|
||||||
|
if err := client.lim.Wait(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
tree, err = client.client.ReconstructTree(ctx, sth)
|
tree, err = client.client.ReconstructTree(ctx, sth)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -184,6 +209,7 @@ func newLogClient(config *Config, ctlog *loglist.Log) (ctclient.Log, ctclient.Is
|
|||||||
config: config,
|
config: config,
|
||||||
log: ctlog,
|
log: ctlog,
|
||||||
client: &ctclient.RFC6962Log{URL: logURL},
|
client: &ctclient.RFC6962Log{URL: logURL},
|
||||||
|
lim: rate.NewLimiter(downloadRateLimit(ctlog), 1),
|
||||||
}, nil, nil
|
}, nil, nil
|
||||||
case ctlog.IsStaticCTAPI():
|
case ctlog.IsStaticCTAPI():
|
||||||
submissionURL, err := url.Parse(ctlog.SubmissionURL)
|
submissionURL, err := url.Parse(ctlog.SubmissionURL)
|
||||||
@@ -203,6 +229,7 @@ func newLogClient(config *Config, ctlog *loglist.Log) (ctclient.Log, ctclient.Is
|
|||||||
config: config,
|
config: config,
|
||||||
log: ctlog,
|
log: ctlog,
|
||||||
client: client,
|
client: client,
|
||||||
|
lim: rate.NewLimiter(downloadRateLimit(ctlog), 1),
|
||||||
}, &issuerGetter{
|
}, &issuerGetter{
|
||||||
config: config,
|
config: config,
|
||||||
log: ctlog,
|
log: ctlog,
|
||||||
|
|||||||
Reference in New Issue
Block a user