Use OCSP NextUpdate to calculate Redis TTL (#6031)

This commit is contained in:
Andrew Gabbitas 2022-04-04 15:18:11 -06:00 committed by GitHub
parent ed912c3aa5
commit 87ef1b4934
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 20 additions and 51 deletions

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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()

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}