Deprecate ROCSP Stage 1, 2, and 3 flags (#6460)
These flags are set in both staging and prod. Deprecate them, make all code gated behind them the only path, and delete code (multi_source) which was only accessible when these flags were not set. Part of #6285
This commit is contained in:
parent
410732e8a7
commit
30d8f19895
|
|
@ -166,60 +166,43 @@ as generated by Boulder's ceremony command.
|
|||
dbMap, err := sa.InitWrappedDb(config.DB, stats, logger)
|
||||
cmd.FailOnError(err, "While initializing dbMap")
|
||||
|
||||
source, err = responder.NewDbSource(dbMap, stats, logger)
|
||||
cmd.FailOnError(err, "Could not create database source")
|
||||
// Set up the redis source and the combined multiplex source.
|
||||
rocspReader, err := rocsp_config.MakeClient(&c.OCSPResponder.Redis, clk, stats)
|
||||
cmd.FailOnError(err, "Could not make redis client")
|
||||
|
||||
// Set up the redis source and the combined multiplex source if there
|
||||
// is a config for it and the feature flag is enabled. Otherwise
|
||||
// just pass through the existing mysql source.
|
||||
if c.OCSPResponder.Redis.Addrs != nil && features.Enabled(features.ROCSPStage1) {
|
||||
rocspReader, err := rocsp_config.MakeClient(&c.OCSPResponder.Redis, clk, stats)
|
||||
cmd.FailOnError(err, "Could not make redis client")
|
||||
err = rocspReader.Ping(context.Background())
|
||||
cmd.FailOnError(err, "pinging Redis")
|
||||
|
||||
err = rocspReader.Ping(context.Background())
|
||||
cmd.FailOnError(err, "pinging Redis")
|
||||
|
||||
expectedFreshness := c.OCSPResponder.ExpectedFreshness.Duration
|
||||
if expectedFreshness == 0 {
|
||||
expectedFreshness = 61 * time.Hour
|
||||
}
|
||||
|
||||
liveSigningPeriod := c.OCSPResponder.LiveSigningPeriod.Duration
|
||||
if liveSigningPeriod == 0 {
|
||||
liveSigningPeriod = 60 * time.Hour
|
||||
}
|
||||
|
||||
tlsConfig, err := c.OCSPResponder.TLS.Load()
|
||||
cmd.FailOnError(err, "TLS config")
|
||||
clientMetrics := bgrpc.NewClientMetrics(stats)
|
||||
raConn, err := bgrpc.ClientSetup(c.OCSPResponder.RAService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA")
|
||||
rac := rapb.NewRegistrationAuthorityClient(raConn)
|
||||
|
||||
maxInflight := c.OCSPResponder.MaxInflightSignings
|
||||
if maxInflight == 0 {
|
||||
maxInflight = 1000
|
||||
}
|
||||
liveSource := live.New(rac, int64(maxInflight))
|
||||
|
||||
rocspSource, err := redis_responder.NewRedisSource(rocspReader, liveSource, liveSigningPeriod, clk, stats, logger)
|
||||
cmd.FailOnError(err, "Could not create redis source")
|
||||
|
||||
if features.Enabled(features.ROCSPStage3) {
|
||||
var sac sapb.StorageAuthorityClient
|
||||
if c.OCSPResponder.SAService != nil {
|
||||
saConn, err := bgrpc.ClientSetup(c.OCSPResponder.SAService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
|
||||
sac = sapb.NewStorageAuthorityClient(saConn)
|
||||
}
|
||||
source, err = redis_responder.NewCheckedRedisSource(rocspSource, dbMap, sac, stats, logger)
|
||||
cmd.FailOnError(err, "Could not create checkedRedis source")
|
||||
} else {
|
||||
source, err = responder.NewMultiSource(source, rocspSource, expectedFreshness, stats, logger)
|
||||
cmd.FailOnError(err, "Could not create multiplex source")
|
||||
}
|
||||
liveSigningPeriod := c.OCSPResponder.LiveSigningPeriod.Duration
|
||||
if liveSigningPeriod == 0 {
|
||||
liveSigningPeriod = 60 * time.Hour
|
||||
}
|
||||
|
||||
tlsConfig, err := c.OCSPResponder.TLS.Load()
|
||||
cmd.FailOnError(err, "TLS config")
|
||||
clientMetrics := bgrpc.NewClientMetrics(stats)
|
||||
raConn, err := bgrpc.ClientSetup(c.OCSPResponder.RAService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA")
|
||||
rac := rapb.NewRegistrationAuthorityClient(raConn)
|
||||
|
||||
maxInflight := c.OCSPResponder.MaxInflightSignings
|
||||
if maxInflight == 0 {
|
||||
maxInflight = 1000
|
||||
}
|
||||
liveSource := live.New(rac, int64(maxInflight))
|
||||
|
||||
rocspSource, err := redis_responder.NewRedisSource(rocspReader, liveSource, liveSigningPeriod, clk, stats, logger)
|
||||
cmd.FailOnError(err, "Could not create redis source")
|
||||
|
||||
var sac sapb.StorageAuthorityClient
|
||||
if c.OCSPResponder.SAService != nil {
|
||||
saConn, err := bgrpc.ClientSetup(c.OCSPResponder.SAService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
|
||||
sac = sapb.NewStorageAuthorityClient(saConn)
|
||||
}
|
||||
source, err = redis_responder.NewCheckedRedisSource(rocspSource, dbMap, sac, stats, logger)
|
||||
cmd.FailOnError(err, "Could not create checkedRedis source")
|
||||
|
||||
// Load the certificate from the file path.
|
||||
issuerCerts := make([]*issuance.Certificate, len(c.OCSPResponder.IssuerCerts))
|
||||
for i, issuerFile := range c.OCSPResponder.IssuerCerts {
|
||||
|
|
|
|||
|
|
@ -16,36 +16,36 @@ func _() {
|
|||
_ = x[StreamlineOrderAndAuthzs-5]
|
||||
_ = x[V1DisableNewValidations-6]
|
||||
_ = x[ExpirationMailerDontLookTwice-7]
|
||||
_ = x[CAAValidationMethods-8]
|
||||
_ = x[CAAAccountURI-9]
|
||||
_ = x[EnforceMultiVA-10]
|
||||
_ = x[MultiVAFullResults-11]
|
||||
_ = x[MandatoryPOSTAsGET-12]
|
||||
_ = x[AllowV1Registration-13]
|
||||
_ = x[StoreRevokerInfo-14]
|
||||
_ = x[RestrictRSAKeySizes-15]
|
||||
_ = x[FasterNewOrdersRateLimit-16]
|
||||
_ = x[ECDSAForAll-17]
|
||||
_ = x[ServeRenewalInfo-18]
|
||||
_ = x[GetAuthzReadOnly-19]
|
||||
_ = x[GetAuthzUseIndex-20]
|
||||
_ = x[CheckFailedAuthorizationsFirst-21]
|
||||
_ = x[AllowReRevocation-22]
|
||||
_ = x[MozRevocationReasons-23]
|
||||
_ = x[OldTLSOutbound-24]
|
||||
_ = x[OldTLSInbound-25]
|
||||
_ = x[SHA1CSRs-26]
|
||||
_ = x[AllowUnrecognizedFeatures-27]
|
||||
_ = x[RejectDuplicateCSRExtensions-28]
|
||||
_ = x[ROCSPStage1-29]
|
||||
_ = x[ROCSPStage2-30]
|
||||
_ = x[ROCSPStage3-31]
|
||||
_ = x[ROCSPStage1-8]
|
||||
_ = x[ROCSPStage2-9]
|
||||
_ = x[ROCSPStage3-10]
|
||||
_ = x[CAAValidationMethods-11]
|
||||
_ = x[CAAAccountURI-12]
|
||||
_ = x[EnforceMultiVA-13]
|
||||
_ = x[MultiVAFullResults-14]
|
||||
_ = x[MandatoryPOSTAsGET-15]
|
||||
_ = x[AllowV1Registration-16]
|
||||
_ = x[StoreRevokerInfo-17]
|
||||
_ = x[RestrictRSAKeySizes-18]
|
||||
_ = x[FasterNewOrdersRateLimit-19]
|
||||
_ = x[ECDSAForAll-20]
|
||||
_ = x[ServeRenewalInfo-21]
|
||||
_ = x[GetAuthzReadOnly-22]
|
||||
_ = x[GetAuthzUseIndex-23]
|
||||
_ = x[CheckFailedAuthorizationsFirst-24]
|
||||
_ = x[AllowReRevocation-25]
|
||||
_ = x[MozRevocationReasons-26]
|
||||
_ = x[OldTLSOutbound-27]
|
||||
_ = x[OldTLSInbound-28]
|
||||
_ = x[SHA1CSRs-29]
|
||||
_ = x[AllowUnrecognizedFeatures-30]
|
||||
_ = x[RejectDuplicateCSRExtensions-31]
|
||||
_ = x[ROCSPStage6-32]
|
||||
}
|
||||
|
||||
const _FeatureFlag_name = "unusedPrecertificateRevocationStripDefaultSchemePortNonCFSSLSignerStoreIssuerInfoStreamlineOrderAndAuthzsV1DisableNewValidationsExpirationMailerDontLookTwiceCAAValidationMethodsCAAAccountURIEnforceMultiVAMultiVAFullResultsMandatoryPOSTAsGETAllowV1RegistrationStoreRevokerInfoRestrictRSAKeySizesFasterNewOrdersRateLimitECDSAForAllServeRenewalInfoGetAuthzReadOnlyGetAuthzUseIndexCheckFailedAuthorizationsFirstAllowReRevocationMozRevocationReasonsOldTLSOutboundOldTLSInboundSHA1CSRsAllowUnrecognizedFeaturesRejectDuplicateCSRExtensionsROCSPStage1ROCSPStage2ROCSPStage3ROCSPStage6"
|
||||
const _FeatureFlag_name = "unusedPrecertificateRevocationStripDefaultSchemePortNonCFSSLSignerStoreIssuerInfoStreamlineOrderAndAuthzsV1DisableNewValidationsExpirationMailerDontLookTwiceROCSPStage1ROCSPStage2ROCSPStage3CAAValidationMethodsCAAAccountURIEnforceMultiVAMultiVAFullResultsMandatoryPOSTAsGETAllowV1RegistrationStoreRevokerInfoRestrictRSAKeySizesFasterNewOrdersRateLimitECDSAForAllServeRenewalInfoGetAuthzReadOnlyGetAuthzUseIndexCheckFailedAuthorizationsFirstAllowReRevocationMozRevocationReasonsOldTLSOutboundOldTLSInboundSHA1CSRsAllowUnrecognizedFeaturesRejectDuplicateCSRExtensionsROCSPStage6"
|
||||
|
||||
var _FeatureFlag_index = [...]uint16{0, 6, 30, 52, 66, 81, 105, 128, 157, 177, 190, 204, 222, 240, 259, 275, 294, 318, 329, 345, 361, 377, 407, 424, 444, 458, 471, 479, 504, 532, 543, 554, 565, 576}
|
||||
var _FeatureFlag_index = [...]uint16{0, 6, 30, 52, 66, 81, 105, 128, 157, 168, 179, 190, 210, 223, 237, 255, 273, 292, 308, 327, 351, 362, 378, 394, 410, 440, 457, 477, 491, 504, 512, 537, 565, 576}
|
||||
|
||||
func (i FeatureFlag) String() string {
|
||||
if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ const (
|
|||
StreamlineOrderAndAuthzs
|
||||
V1DisableNewValidations
|
||||
ExpirationMailerDontLookTwice
|
||||
ROCSPStage1
|
||||
ROCSPStage2
|
||||
ROCSPStage3
|
||||
|
||||
// Currently in-use features
|
||||
// Check CAA and respect validationmethods parameter.
|
||||
|
|
@ -98,20 +101,6 @@ const (
|
|||
// go1.19.
|
||||
RejectDuplicateCSRExtensions
|
||||
|
||||
// ROCSPStage1 enables querying Redis, live-signing response, and storing
|
||||
// to Redis, but doesn't serve responses from Redis.
|
||||
ROCSPStage1
|
||||
// ROCSPStage2 enables querying Redis, live-signing a response, and storing
|
||||
// to Redis, and does serve responses from Redis when appropriate (when
|
||||
// they are fresh, and agree with MariaDB's status for the certificate).
|
||||
ROCSPStage2
|
||||
// ROCSPStage3 enables querying Redis, live-signing a response, and serving
|
||||
// from Redis, without any fallback to serving bytes from MariaDB. In this
|
||||
// mode we still make a parallel request to MariaDB to cross-check the
|
||||
// _status_ of the response. If that request indicates a different status
|
||||
// than what's stored in Redis, we'll trigger a fresh signing and serve and
|
||||
// store the result.
|
||||
ROCSPStage3
|
||||
// ROCSPStage6 disables writing full OCSP Responses to MariaDB during
|
||||
// (pre)certificate issuance and during revocation. Because Stage 4 involved
|
||||
// disabling ocsp-updater, this means that no ocsp response bytes will be
|
||||
|
|
|
|||
|
|
@ -1,204 +0,0 @@
|
|||
package responder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/features"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/rocsp"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
type multiSource struct {
|
||||
primary Source
|
||||
secondary Source
|
||||
expectedFreshness time.Duration
|
||||
counter *prometheus.CounterVec
|
||||
checkSecondaryCounter *prometheus.CounterVec
|
||||
log blog.Logger
|
||||
}
|
||||
|
||||
// NewMultiSource creates a source that combines a primary and a secondary source.
|
||||
//
|
||||
// It performs lookups using both the primary and secondary Sources.
|
||||
// It always waits for a response from the primary. If the primary response is
|
||||
// stale (older than expectedFreshness), it will wait for a "better" response
|
||||
// from the secondary.
|
||||
//
|
||||
// The secondary response will be served only if (a) it has the same status as
|
||||
// the primary response (good or revoked), and (b) it is fresher than the
|
||||
// primary response.
|
||||
//
|
||||
// A stale response from the primary will still be served if there is no
|
||||
// better response available from the secondary (due to error, timeout, etc).
|
||||
func NewMultiSource(primary, secondary Source, expectedFreshness time.Duration, stats prometheus.Registerer, log blog.Logger) (*multiSource, error) {
|
||||
if primary == nil || secondary == nil {
|
||||
return nil, errors.New("must provide both primary and secondary sources")
|
||||
}
|
||||
|
||||
counter := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "ocsp_multiplex_responses",
|
||||
Help: "Count of OCSP requests/responses by action taken by the multiSource",
|
||||
}, []string{"result"})
|
||||
stats.MustRegister(counter)
|
||||
|
||||
checkSecondaryCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "ocsp_multiplex_check_secondary",
|
||||
Help: "Count of OCSP requests/responses by action taken by the multiSource",
|
||||
}, []string{"result"})
|
||||
stats.MustRegister(checkSecondaryCounter)
|
||||
|
||||
return &multiSource{
|
||||
primary: primary,
|
||||
secondary: secondary,
|
||||
expectedFreshness: expectedFreshness,
|
||||
counter: counter,
|
||||
checkSecondaryCounter: checkSecondaryCounter,
|
||||
log: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Response implements the Source interface.
|
||||
func (src *multiSource) Response(ctx context.Context, req *ocsp.Request) (*Response, error) {
|
||||
primaryChan := getResponse(ctx, src.primary, req)
|
||||
|
||||
// Use a separate context for the secondary source. This prevents cancellations
|
||||
// from reaching the backend layer (Redis) and causing connections to be closed
|
||||
// unnecessarily.
|
||||
// https://blog.uptrace.dev/posts/go-context-timeout.html
|
||||
redisCtx := context.Background()
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok {
|
||||
// We don't call the CancelFunc returned by WithDeadline because it
|
||||
// would defeat the purpose. That leaks the context, but only until
|
||||
// the deadline is reached.
|
||||
////nolint:govet
|
||||
redisCtx, _ = context.WithDeadline(redisCtx, deadline)
|
||||
}
|
||||
|
||||
secondaryChan := getResponse(redisCtx, src.secondary, req)
|
||||
|
||||
var primaryResponse *Response
|
||||
|
||||
// If the primary source returns first, check the output and return
|
||||
// it. If the secondary source wins, then wait for the primary so the
|
||||
// results from the secondary can be verified. It is important that we
|
||||
// never return a response from the secondary source that is good if the
|
||||
// primary has a revoked status. If the secondary source wins the race and
|
||||
// passes these checks, return its response instead.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
src.counter.WithLabelValues("primary_timed_out").Inc()
|
||||
return nil, ctx.Err()
|
||||
|
||||
case r := <-primaryChan:
|
||||
// If there was an error requesting from the primary, don't bother
|
||||
// waiting for the secondary, because we wouldn't be able to
|
||||
// check the secondary's status against the (more reliable) primary's
|
||||
// status.
|
||||
if r.err != nil {
|
||||
if errors.Is(r.err, ErrNotFound) {
|
||||
src.counter.WithLabelValues("primary_not_found").Inc()
|
||||
} else {
|
||||
src.counter.WithLabelValues("primary_error").Inc()
|
||||
}
|
||||
return nil, r.err
|
||||
}
|
||||
primaryResponse = r.resp
|
||||
}
|
||||
|
||||
// The primary response was fresh enough to serve, go ahead and serve it.
|
||||
if time.Since(primaryResponse.ThisUpdate) < src.expectedFreshness {
|
||||
src.checkSecondary(secondaryChan)
|
||||
src.counter.WithLabelValues("primary_result").Inc()
|
||||
return primaryResponse, nil
|
||||
}
|
||||
|
||||
// The primary response was too stale to (ideally) serve. This will be
|
||||
// a common path once we stop ocsp-updater from writing updated blobs
|
||||
// to MariaDB. Try to serve from the secondary.
|
||||
var secondaryResponse *Response
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
src.counter.WithLabelValues("timed_out_awaiting_secondary").Inc()
|
||||
// Best-effort: return the primary response even though it's stale.
|
||||
return primaryResponse, nil
|
||||
|
||||
case secondaryResult := <-secondaryChan:
|
||||
if secondaryResult.err != nil {
|
||||
if errors.Is(secondaryResult.err, rocsp.ErrRedisNotFound) {
|
||||
// This case will happen for several hours after first issuance.
|
||||
src.counter.WithLabelValues("primary_stale_secondary_not_found").Inc()
|
||||
} else {
|
||||
src.counter.WithLabelValues("primary_stale_secondary_error").Inc()
|
||||
}
|
||||
|
||||
// Best-effort: return the primary response even though it's stale.
|
||||
return primaryResponse, nil
|
||||
}
|
||||
secondaryResponse = secondaryResult.resp
|
||||
}
|
||||
|
||||
// If the secondary response status doesn't match primary, return
|
||||
// primary response. For instance this will happen for several hours
|
||||
// after any revocation.
|
||||
if secondaryResponse.Status != primaryResponse.Status {
|
||||
src.counter.WithLabelValues("primary_stale_status_wins").Inc()
|
||||
return primaryResponse, nil
|
||||
}
|
||||
|
||||
// ROCSP Stage 2 enables serving responses from Redis
|
||||
if features.Enabled(features.ROCSPStage2) {
|
||||
src.counter.WithLabelValues("secondary").Inc()
|
||||
return secondaryResponse, nil
|
||||
}
|
||||
|
||||
src.counter.WithLabelValues("primary").Inc()
|
||||
return primaryResponse, nil
|
||||
}
|
||||
|
||||
// checkSecondary updates the src.counter metrics when we're planning to return
|
||||
// a primary response. It checks if the secondary result has already arrived
|
||||
// (without blocking on it) and updates the metrics accordingly.
|
||||
func (src *multiSource) checkSecondary(secondaryChan <-chan responseResult) {
|
||||
select {
|
||||
case secondaryResult := <-secondaryChan:
|
||||
if secondaryResult.err != nil {
|
||||
if errors.Is(secondaryResult.err, rocsp.ErrRedisNotFound) {
|
||||
// This case will happen for several hours after first issuance.
|
||||
src.checkSecondaryCounter.WithLabelValues("not_found").Inc()
|
||||
} else {
|
||||
src.checkSecondaryCounter.WithLabelValues("error").Inc()
|
||||
}
|
||||
}
|
||||
src.checkSecondaryCounter.WithLabelValues("good").Inc()
|
||||
default:
|
||||
src.checkSecondaryCounter.WithLabelValues("slow").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
type responseResult struct {
|
||||
resp *Response
|
||||
err error
|
||||
}
|
||||
|
||||
// getResponse provides a thin wrapper around an underlying Source's Response
|
||||
// method, calling it in a goroutine and passing the result back on a channel.
|
||||
func getResponse(ctx context.Context, src Source, req *ocsp.Request) chan responseResult {
|
||||
// Use a buffer so the following goroutine can exit as soon as it's done,
|
||||
// rather than blocking on a reader (which would introduce a risk that the
|
||||
// other never reads, leaking the goroutine).
|
||||
responseChan := make(chan responseResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(responseChan)
|
||||
|
||||
resp, err := src.Response(ctx, req)
|
||||
responseChan <- responseResult{resp, err}
|
||||
}()
|
||||
|
||||
return responseChan
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
package responder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/features"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
const expectedFreshness = 61 * time.Hour
|
||||
|
||||
type ok struct{}
|
||||
|
||||
func (src ok) Response(context.Context, *ocsp.Request) (*Response, error) {
|
||||
return &Response{
|
||||
Response: &ocsp.Response{
|
||||
Status: ocsp.Good,
|
||||
ThisUpdate: time.Now().Add(-10 * time.Hour),
|
||||
},
|
||||
Raw: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type revoked struct{}
|
||||
|
||||
func (src revoked) Response(context.Context, *ocsp.Request) (*Response, error) {
|
||||
return &Response{
|
||||
Response: &ocsp.Response{
|
||||
Status: ocsp.Revoked,
|
||||
ThisUpdate: time.Now().Add(-10 * time.Hour),
|
||||
},
|
||||
Raw: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type stale struct{}
|
||||
|
||||
func (src stale) Response(context.Context, *ocsp.Request) (*Response, error) {
|
||||
return &Response{
|
||||
Response: &ocsp.Response{
|
||||
Status: ocsp.Good,
|
||||
ThisUpdate: time.Now().Add(-70 * time.Hour),
|
||||
},
|
||||
Raw: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type fail struct{}
|
||||
|
||||
func (src fail) Response(context.Context, *ocsp.Request) (*Response, error) {
|
||||
return nil, errors.New("failure")
|
||||
}
|
||||
|
||||
// timeout is a Source that will not return until its chan is closed.
|
||||
type timeout struct {
|
||||
ch <-chan struct{}
|
||||
}
|
||||
|
||||
func (src timeout) Response(context.Context, *ocsp.Request) (*Response, error) {
|
||||
<-src.ch
|
||||
return nil, errors.New("failure")
|
||||
}
|
||||
|
||||
func TestMultiSource(t *testing.T) {
|
||||
type testCase struct {
|
||||
primary Source
|
||||
secondary Source
|
||||
expectedError bool
|
||||
expectedStatus int // only checked if expectedError is false
|
||||
}
|
||||
ignored := 99
|
||||
cases := map[string]testCase{
|
||||
"ok-ok": {ok{}, ok{}, false, ocsp.Good},
|
||||
"ok-fail": {ok{}, fail{}, false, ocsp.Good},
|
||||
"ok-revoked": {ok{}, revoked{}, false, ocsp.Good},
|
||||
"ok-stale": {ok{}, stale{}, false, ocsp.Good},
|
||||
"ok-timeout": {ok{}, timeout{}, false, ocsp.Good},
|
||||
"fail-ok": {fail{}, ok{}, true, ignored},
|
||||
"fail-fail": {fail{}, fail{}, true, ignored},
|
||||
"fail-revoked": {fail{}, revoked{}, true, ignored},
|
||||
"fail-stale": {fail{}, stale{}, true, ignored},
|
||||
"fail-timeout": {fail{}, timeout{}, true, ignored},
|
||||
"revoked-ok": {revoked{}, ok{}, false, ocsp.Revoked},
|
||||
"revoked-fail": {revoked{}, fail{}, false, ocsp.Revoked},
|
||||
"revoked-revoked": {revoked{}, revoked{}, false, ocsp.Revoked},
|
||||
"revoked-stale": {revoked{}, stale{}, false, ocsp.Revoked},
|
||||
"revoked-timeout": {revoked{}, timeout{}, false, ocsp.Revoked},
|
||||
"stale-ok": {stale{}, ok{}, false, ocsp.Good},
|
||||
"stale-fail": {stale{}, fail{}, false, ocsp.Good},
|
||||
"stale-revoked": {stale{}, revoked{}, false, ocsp.Good},
|
||||
"stale-stale": {stale{}, stale{}, false, ocsp.Good},
|
||||
"stale-timeout": {stale{}, timeout{}, false, ocsp.Good},
|
||||
"timeout-ok": {timeout{}, ok{}, true, ignored},
|
||||
"timeout-fail": {timeout{}, fail{}, true, ignored},
|
||||
"timeout-revoked": {timeout{}, revoked{}, true, ignored},
|
||||
"timeout-stale": {timeout{}, stale{}, true, ignored},
|
||||
"timeout-timeout": {timeout{}, timeout{}, true, ignored},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
src, err := NewMultiSource(tc.primary, tc.secondary, expectedFreshness, metrics.NoopRegisterer, blog.NewMock())
|
||||
test.AssertNotError(t, err, "failed to create multiSource")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
resp, err := src.Response(ctx, &ocsp.Request{})
|
||||
if err != nil {
|
||||
if !tc.expectedError {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if tc.expectedError {
|
||||
t.Errorf("expected error, got none")
|
||||
}
|
||||
if resp.Status != tc.expectedStatus {
|
||||
t.Errorf("expected response status %d, got %d", tc.expectedStatus, resp.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecondaryTimeout(t *testing.T) {
|
||||
ch := make(chan struct{})
|
||||
src, err := NewMultiSource(&ok{}, &timeout{ch: ch}, expectedFreshness, metrics.NoopRegisterer, blog.NewMock())
|
||||
test.AssertNotError(t, err, "failed to create multiSource")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
starting_goroutines := runtime.NumGoroutine()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
_, err = src.Response(ctx, &ocsp.Request{})
|
||||
test.AssertNotError(t, err, "unexpected error")
|
||||
}
|
||||
|
||||
close(ch)
|
||||
// Wait for the goroutines to exit
|
||||
time.Sleep(40 * time.Millisecond)
|
||||
goroutine_diff := runtime.NumGoroutine() - starting_goroutines
|
||||
if goroutine_diff > 0 {
|
||||
t.Fatalf("expected no lingering goroutines. found %d", goroutine_diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrimaryStale(t *testing.T) {
|
||||
err := features.Set(map[string]bool{
|
||||
"ROCSPStage2": true,
|
||||
})
|
||||
test.AssertNotError(t, err, "setting features")
|
||||
|
||||
src, err := NewMultiSource(stale{}, ok{}, expectedFreshness, metrics.NoopRegisterer, blog.NewMock())
|
||||
test.AssertNotError(t, err, "failed to create multiSource")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
resp, err := src.Response(ctx, &ocsp.Request{})
|
||||
test.AssertNotError(t, err, "getting response")
|
||||
|
||||
age := time.Since(resp.ThisUpdate)
|
||||
if age > expectedFreshness {
|
||||
t.Errorf("expected response to be fresh, but it was %s old", age)
|
||||
}
|
||||
}
|
||||
|
|
@ -1769,7 +1769,7 @@ func TestRevokeCertificateNoResponse(t *testing.T) {
|
|||
Date: now.UnixNano(),
|
||||
Reason: reason,
|
||||
})
|
||||
test.AssertNotError(t, err, "RevokeCertificate should succeed with no response when ROCSPStage3 is enabled")
|
||||
test.AssertNotError(t, err, "RevokeCertificate should succeed with no response when ROCSPStage6 is enabled")
|
||||
}
|
||||
|
||||
func TestUpdateRevokedCertificate(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -48,17 +48,12 @@
|
|||
"/hierarchy/intermediate-cert-rsa-b.pem",
|
||||
"/hierarchy/intermediate-cert-ecdsa-a.pem"
|
||||
],
|
||||
"expectedFreshness": "61h",
|
||||
"liveSigningPeriod": "60h",
|
||||
"timeout": "4.9s",
|
||||
"shutdownStopTimeout": "10s",
|
||||
"debugAddr": ":8005",
|
||||
"requiredSerialPrefixes": ["ff"],
|
||||
"features": {
|
||||
"ROCSPStage1": true,
|
||||
"ROCSPStage2": true,
|
||||
"ROCSPStage3": true
|
||||
}
|
||||
"features": {}
|
||||
},
|
||||
|
||||
"syslog": {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,43 @@
|
|||
"dbConnectFile": "test/secrets/ocsp_responder_dburl",
|
||||
"maxOpenConns": 10
|
||||
},
|
||||
"redis": {
|
||||
"username": "ocsp-responder",
|
||||
"passwordFile": "test/secrets/ocsp_responder_redis_password",
|
||||
"addrs": [
|
||||
"10.33.33.7:4218"
|
||||
],
|
||||
"timeout": "5s",
|
||||
"poolSize": 100,
|
||||
"routeRandomly": true,
|
||||
"tls": {
|
||||
"caCertFile": "test/redis-tls/minica.pem",
|
||||
"certFile": "test/redis-tls/boulder/cert.pem",
|
||||
"keyFile": "test/redis-tls/boulder/key.pem"
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"caCertFile": "test/grpc-creds/minica.pem",
|
||||
"certFile": "test/grpc-creds/ocsp-responder.boulder/cert.pem",
|
||||
"keyFile": "test/grpc-creds/ocsp-responder.boulder/key.pem"
|
||||
},
|
||||
"raService": {
|
||||
"srvLookup": {
|
||||
"service": "ra",
|
||||
"domain": "service.consul"
|
||||
},
|
||||
"hostOverride": "ra.boulder",
|
||||
"timeout": "15s"
|
||||
},
|
||||
"saService": {
|
||||
"srvLookup": {
|
||||
"service": "sa",
|
||||
"domain": "service.consul"
|
||||
},
|
||||
"timeout": "15s",
|
||||
"hostOverride": "sa.boulder"
|
||||
},
|
||||
"logSampleRate": 1,
|
||||
"path": "/",
|
||||
"listenAddress": "0.0.0.0:4002",
|
||||
"issuerCerts": [
|
||||
|
|
@ -11,15 +48,17 @@
|
|||
"/hierarchy/intermediate-cert-rsa-b.pem",
|
||||
"/hierarchy/intermediate-cert-ecdsa-a.pem"
|
||||
],
|
||||
"liveSigningPeriod": "60h",
|
||||
"timeout": "4.9s",
|
||||
"shutdownStopTimeout": "10s",
|
||||
"debugAddr": ":8005",
|
||||
"requiredSerialPrefixes": ["ff"]
|
||||
"requiredSerialPrefixes": ["ff"],
|
||||
"features": {}
|
||||
},
|
||||
|
||||
"syslog": {
|
||||
"stdoutlevel": 6,
|
||||
"sysloglevel": 6
|
||||
"sysloglevel": -1
|
||||
},
|
||||
"beeline": {
|
||||
"mute": true,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
"admin-revoker.boulder",
|
||||
"bad-key-revoker.boulder",
|
||||
"health-checker.boulder",
|
||||
"ocsp-responder.boulder",
|
||||
"wfe.boulder"
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"crl-updater.boulder",
|
||||
"expiration-mailer.boulder",
|
||||
"health-checker.boulder",
|
||||
"ocsp-responder.boulder",
|
||||
"orphan-finder.boulder",
|
||||
"ra.boulder",
|
||||
"sa.boulder",
|
||||
|
|
|
|||
Loading…
Reference in New Issue