Use OCSP NextUpdate to calculate Redis TTL (#6031)
This commit is contained in:
parent
ed912c3aa5
commit
87ef1b4934
|
|
@ -218,15 +218,13 @@ func (cl *client) signAndStoreResponses(ctx context.Context, input <-chan *sa.Ce
|
|||
output <- processResult{id: uint64(status.ID), err: err}
|
||||
continue
|
||||
}
|
||||
// ttl is the lifetime of the certificate
|
||||
ttl := cl.clk.Now().Sub(status.NotAfter)
|
||||
issuer, err := rocsp_config.FindIssuerByID(status.IssuerID, cl.issuers)
|
||||
if err != nil {
|
||||
output <- processResult{id: uint64(status.ID), err: err}
|
||||
continue
|
||||
}
|
||||
|
||||
err = cl.redis.StoreResponse(ctx, result.Response, issuer.ShortID(), ttl)
|
||||
err = cl.redis.StoreResponse(ctx, result.Response, issuer.ShortID())
|
||||
if err != nil {
|
||||
output <- processResult{id: uint64(status.ID), err: err}
|
||||
} else {
|
||||
|
|
@ -250,7 +248,7 @@ func (cl *client) storeResponsesFromFiles(ctx context.Context, files []string) e
|
|||
if err != nil {
|
||||
return fmt.Errorf("reading response file %q: %w", respFile, err)
|
||||
}
|
||||
err = cl.storeResponse(ctx, respBytes, nil)
|
||||
err = cl.storeResponse(ctx, respBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -258,7 +256,7 @@ func (cl *client) storeResponsesFromFiles(ctx context.Context, files []string) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cl *client) storeResponse(ctx context.Context, respBytes []byte, ttl *time.Duration) error {
|
||||
func (cl *client) storeResponse(ctx context.Context, respBytes []byte) error {
|
||||
resp, err := ocsp.ParseResponse(respBytes, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing response: %w", err)
|
||||
|
|
@ -283,23 +281,13 @@ func (cl *client) storeResponse(ctx context.Context, respBytes []byte, ttl *time
|
|||
}
|
||||
}
|
||||
|
||||
// Note: Here we set the TTL to slightly more than the lifetime of the
|
||||
// OCSP response. In ocsp-updater we'll want to set it to the lifetime
|
||||
// of the certificate, so that the metadata field doesn't fall out of
|
||||
// storage even if we are down for days. However, in this tool we don't
|
||||
// have the full certificate, so this will do.
|
||||
if ttl == nil {
|
||||
ttl_temp := resp.NextUpdate.Sub(cl.clk.Now()) + time.Hour
|
||||
ttl = &ttl_temp
|
||||
}
|
||||
|
||||
cl.logger.Infof("storing response for %s, generated %s, ttl %g hours",
|
||||
serial,
|
||||
resp.ThisUpdate,
|
||||
ttl.Hours(),
|
||||
time.Until(resp.NextUpdate).Hours(),
|
||||
)
|
||||
|
||||
err = cl.redis.StoreResponse(ctx, respBytes, issuer.ShortID(), *ttl)
|
||||
err = cl.redis.StoreResponse(ctx, respBytes, issuer.ShortID())
|
||||
if err != nil {
|
||||
return fmt.Errorf("storing response: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,8 +111,7 @@ func TestStoreResponse(t *testing.T) {
|
|||
logger: blog.NewMock(),
|
||||
}
|
||||
|
||||
ttl := time.Hour
|
||||
err = cl.storeResponse(context.Background(), response, &ttl)
|
||||
err = cl.storeResponse(context.Background(), response)
|
||||
test.AssertNotError(t, err, "storing response")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type ocspDb interface {
|
|||
}
|
||||
|
||||
type rocspClientInterface interface {
|
||||
StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte, ttl time.Duration) error
|
||||
StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte) error
|
||||
}
|
||||
|
||||
// failCounter provides a concurrent safe counter.
|
||||
|
|
@ -353,13 +353,12 @@ func (updater *OCSPUpdater) storeResponse(ctx context.Context, status *sa.CertSt
|
|||
ctx2, cancel := context.WithTimeout(context.Background(), updater.redisTimeout+time.Second)
|
||||
go func() {
|
||||
defer cancel()
|
||||
ttl := status.NotAfter.Sub(updater.clk.Now())
|
||||
shortIssuerID, err := rocsp_config.FindIssuerByID(status.IssuerID, updater.issuers)
|
||||
if err != nil {
|
||||
updater.storedRedisCounter.WithLabelValues("missing issuer").Inc()
|
||||
return
|
||||
}
|
||||
err = updater.rocspClient.StoreResponse(ctx2, status.OCSPResponse, shortIssuerID.ShortID(), ttl)
|
||||
err = updater.rocspClient.StoreResponse(ctx2, status.OCSPResponse, shortIssuerID.ShortID())
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
updater.storedRedisCounter.WithLabelValues("canceled").Inc()
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func (ca *mockOCSP) GenerateOCSP(_ context.Context, req *capb.GenerateOCSPReques
|
|||
type noopROCSP struct {
|
||||
}
|
||||
|
||||
func (noopROCSP) StoreResponse(_ context.Context, _ []byte, _ byte, _ time.Duration) error {
|
||||
func (noopROCSP) StoreResponse(_ context.Context, _ []byte, _ byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +167,6 @@ func TestGenerateAndStoreOCSPResponse(t *testing.T) {
|
|||
type rocspStorage struct {
|
||||
shortIDIssuer byte
|
||||
response []byte
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
type recordingROCSP struct {
|
||||
|
|
@ -182,13 +181,12 @@ func (rr *recordingROCSP) get() []rocspStorage {
|
|||
return append(ret, rr.storage...)
|
||||
}
|
||||
|
||||
func (rr *recordingROCSP) StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte, ttl time.Duration) error {
|
||||
func (rr *recordingROCSP) StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte) error {
|
||||
rr.Lock()
|
||||
defer rr.Unlock()
|
||||
rr.storage = append(rr.storage, rocspStorage{
|
||||
shortIDIssuer: shortIssuerID,
|
||||
response: respBytes,
|
||||
ttl: ttl,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package rocsp
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MockWriteClient is a mock
|
||||
|
|
@ -13,7 +12,7 @@ type MockWriteClient struct {
|
|||
|
||||
// StoreResponse mocks a rocsp.StoreResponse method and returns nil or an
|
||||
// error depending on the desired state.
|
||||
func (r MockWriteClient) StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte, ttl time.Duration) error {
|
||||
func (r MockWriteClient) StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte) error {
|
||||
return r.StoreReponseReturnError
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,8 +166,9 @@ func NewWritingClient(rdb *redis.ClusterClient, timeout time.Duration, clk clock
|
|||
// into Redis, updating both the metadata and response keys. ShortIssuerID
|
||||
// is an arbitrarily assigned byte that unique identifies each issuer.
|
||||
// Must be the same across OCSP components. Returns error if the OCSP
|
||||
// response fails to parse.
|
||||
func (c *WritingClient) StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte, ttl time.Duration) error {
|
||||
// response fails to parse. The expiration time (ttl) of the Redis key is
|
||||
// set to OCSP response `NextUpdate`.
|
||||
func (c *WritingClient) StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte) error {
|
||||
start := c.clk.Now()
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
|
@ -188,6 +189,9 @@ func (c *WritingClient) StoreResponse(ctx context.Context, respBytes []byte, sho
|
|||
}
|
||||
metadataValue := metadataStruct.Marshal()
|
||||
|
||||
// Set the ttl duration to the response `NextUpdate - now()`
|
||||
ttl := time.Until(resp.NextUpdate)
|
||||
|
||||
err = c.rdb.Watch(ctx, func(tx *redis.Tx) error {
|
||||
err := tx.Set(ctx, responseKey, respBytes, ttl).Err()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func TestSetAndGet(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
var shortIssuerID byte = 99
|
||||
err = client.StoreResponse(context.Background(), response, byte(shortIssuerID), time.Hour)
|
||||
err = client.StoreResponse(context.Background(), response, byte(shortIssuerID))
|
||||
if err != nil {
|
||||
t.Fatalf("storing response: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,31 +2,22 @@ package sa
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
rocsp_config "github.com/letsencrypt/boulder/rocsp/config"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
type rocspWriter interface {
|
||||
StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte, ttl time.Duration) error
|
||||
StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte) error
|
||||
}
|
||||
|
||||
// storeOCSPRedis stores an OCSP response in a redis cluster.
|
||||
func (ssa *SQLStorageAuthority) storeOCSPRedis(ctx context.Context, resp []byte, issuerID int64) error {
|
||||
nextUpdate, err := getNextUpdate(resp)
|
||||
if err != nil {
|
||||
ssa.redisStoreResponse.WithLabelValues("parse_response_error").Inc()
|
||||
return err
|
||||
}
|
||||
ttl := time.Until(nextUpdate)
|
||||
|
||||
shortIssuerID, err := rocsp_config.FindIssuerByID(issuerID, ssa.shortIssuers)
|
||||
if err != nil {
|
||||
ssa.redisStoreResponse.WithLabelValues("find_issuer_error").Inc()
|
||||
return err
|
||||
}
|
||||
err = ssa.rocspWriteClient.StoreResponse(ctx, resp, shortIssuerID.ShortID(), ttl)
|
||||
err = ssa.rocspWriteClient.StoreResponse(ctx, resp, shortIssuerID.ShortID())
|
||||
if err != nil {
|
||||
ssa.redisStoreResponse.WithLabelValues("store_response_error").Inc()
|
||||
return err
|
||||
|
|
@ -34,12 +25,3 @@ func (ssa *SQLStorageAuthority) storeOCSPRedis(ctx context.Context, resp []byte,
|
|||
ssa.redisStoreResponse.WithLabelValues("success").Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNextUpdate returns the NextUpdate value from the OCSP response.
|
||||
func getNextUpdate(resp []byte) (time.Time, error) {
|
||||
response, err := ocsp.ParseResponse(resp, nil)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return response.NextUpdate, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue