Remove deprecated WFE.RedeemNonceServices (#7493)
Fixes https://github.com/letsencrypt/boulder/issues/6610
This commit is contained in:
parent
4663b9898e
commit
5be3650e56
|
|
@ -66,23 +66,13 @@ type Config struct {
|
|||
// used to lookup nonce-service instances used exclusively for nonce
|
||||
// creation. In a multi-DC deployment this should refer to local
|
||||
// nonce-service instances only.
|
||||
GetNonceService *cmd.GRPCClientConfig
|
||||
|
||||
// RedeemNonceServices contains a map of nonce-service prefixes to
|
||||
// gRPC configs we want to use to redeem nonces. In a multi-DC deployment
|
||||
// this should contain all nonce-services from all DCs as we want to be
|
||||
// able to redeem nonces generated at any DC.
|
||||
//
|
||||
// Deprecated: See RedeemNonceService, below.
|
||||
// TODO (#6610) Remove this after all configs have migrated to
|
||||
// `RedeemNonceService`.
|
||||
RedeemNonceServices map[string]cmd.GRPCClientConfig `validate:"required_without=RedeemNonceService,omitempty,min=1,dive"`
|
||||
GetNonceService *cmd.GRPCClientConfig `validate:"required"`
|
||||
|
||||
// RedeemNonceService is a gRPC config which contains a list of SRV
|
||||
// names used to lookup nonce-service instances used exclusively for
|
||||
// nonce redemption. In a multi-DC deployment this should contain both
|
||||
// local and remote nonce-service instances.
|
||||
RedeemNonceService *cmd.GRPCClientConfig `validate:"required_without=RedeemNonceServices"`
|
||||
RedeemNonceService *cmd.GRPCClientConfig `validate:"required"`
|
||||
|
||||
// NoncePrefixKey is a secret used for deriving the prefix of each nonce
|
||||
// instance. It should contain 256 bits of random data to be suitable as
|
||||
|
|
@ -209,7 +199,7 @@ func loadChain(certFiles []string) (*issuance.Certificate, []byte, error) {
|
|||
return certs[0], buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func setupWFE(c Config, scope prometheus.Registerer, clk clock.Clock) (rapb.RegistrationAuthorityClient, sapb.StorageAuthorityReadOnlyClient, nonce.Getter, map[string]nonce.Redeemer, nonce.Redeemer, string) {
|
||||
func setupWFE(c Config, scope prometheus.Registerer, clk clock.Clock) (rapb.RegistrationAuthorityClient, sapb.StorageAuthorityReadOnlyClient, nonce.Getter, nonce.Redeemer, string) {
|
||||
tlsConfig, err := c.WFE.TLS.Load(scope)
|
||||
cmd.FailOnError(err, "TLS config")
|
||||
|
||||
|
|
@ -221,15 +211,8 @@ func setupWFE(c Config, scope prometheus.Registerer, clk clock.Clock) (rapb.Regi
|
|||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
|
||||
sac := sapb.NewStorageAuthorityReadOnlyClient(saConn)
|
||||
|
||||
// TODO(#6610) Refactor these checks.
|
||||
if c.WFE.RedeemNonceService != nil && c.WFE.RedeemNonceServices != nil {
|
||||
cmd.Fail("Only one of 'redeemNonceService' or 'redeemNonceServices' should be configured.")
|
||||
}
|
||||
if c.WFE.RedeemNonceService == nil && c.WFE.RedeemNonceServices == nil {
|
||||
cmd.Fail("One of 'redeemNonceService' or 'redeemNonceServices' must be configured.")
|
||||
}
|
||||
if c.WFE.RedeemNonceService != nil && c.WFE.NoncePrefixKey.PasswordFile == "" {
|
||||
cmd.Fail("'noncePrefixKey' must be configured if 'redeemNonceService' is configured.")
|
||||
if c.WFE.RedeemNonceService == nil {
|
||||
cmd.Fail("'redeemNonceService' must be configured.")
|
||||
}
|
||||
if c.WFE.GetNonceService == nil {
|
||||
cmd.Fail("'getNonceService' must be configured")
|
||||
|
|
@ -245,32 +228,16 @@ func setupWFE(c Config, scope prometheus.Registerer, clk clock.Clock) (rapb.Regi
|
|||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to get nonce service")
|
||||
gnc := nonce.NewGetter(getNonceConn)
|
||||
|
||||
var rnc nonce.Redeemer
|
||||
var npm map[string]nonce.Redeemer
|
||||
if c.WFE.RedeemNonceService != nil {
|
||||
// Dispatch nonce redemption RPCs dynamically.
|
||||
if c.WFE.RedeemNonceService.SRVResolver != noncebalancer.SRVResolverScheme {
|
||||
cmd.Fail(fmt.Sprintf(
|
||||
"'redeemNonceService.SRVResolver' must be set to %q", noncebalancer.SRVResolverScheme),
|
||||
)
|
||||
}
|
||||
redeemNonceConn, err := bgrpc.ClientSetup(c.WFE.RedeemNonceService, tlsConfig, scope, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to redeem nonce service")
|
||||
rnc = nonce.NewRedeemer(redeemNonceConn)
|
||||
} else {
|
||||
// Dispatch nonce redpemption RPCs using a static mapping.
|
||||
//
|
||||
// TODO(#6610) Remove code below and the `npm` mapping.
|
||||
npm = make(map[string]nonce.Redeemer)
|
||||
for prefix, serviceConfig := range c.WFE.RedeemNonceServices {
|
||||
serviceConfig := serviceConfig
|
||||
conn, err := bgrpc.ClientSetup(&serviceConfig, tlsConfig, scope, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to redeem nonce service")
|
||||
npm[prefix] = nonce.NewRedeemer(conn)
|
||||
}
|
||||
if c.WFE.RedeemNonceService.SRVResolver != noncebalancer.SRVResolverScheme {
|
||||
cmd.Fail(fmt.Sprintf(
|
||||
"'redeemNonceService.SRVResolver' must be set to %q", noncebalancer.SRVResolverScheme),
|
||||
)
|
||||
}
|
||||
redeemNonceConn, err := bgrpc.ClientSetup(c.WFE.RedeemNonceService, tlsConfig, scope, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to redeem nonce service")
|
||||
rnc := nonce.NewRedeemer(redeemNonceConn)
|
||||
|
||||
return rac, sac, gnc, npm, rnc, rncKey
|
||||
return rac, sac, gnc, rnc, rncKey
|
||||
}
|
||||
|
||||
type errorWriter struct {
|
||||
|
|
@ -342,7 +309,7 @@ func main() {
|
|||
|
||||
clk := cmd.Clock()
|
||||
|
||||
rac, sac, gnc, npm, rnc, npKey := setupWFE(c, stats, clk)
|
||||
rac, sac, gnc, rnc, npKey := setupWFE(c, stats, clk)
|
||||
|
||||
kp, err := sagoodkey.NewKeyPolicy(&c.WFE.GoodKey, sac.KeyBlocked)
|
||||
cmd.FailOnError(err, "Unable to create key policy")
|
||||
|
|
@ -408,7 +375,6 @@ func main() {
|
|||
rac,
|
||||
sac,
|
||||
gnc,
|
||||
npm,
|
||||
rnc,
|
||||
npKey,
|
||||
accountGetter,
|
||||
|
|
|
|||
|
|
@ -18,29 +18,22 @@ type Config struct {
|
|||
cmd.ServiceConfig
|
||||
|
||||
MaxUsed int
|
||||
// TODO(#6610): Remove once we've moved to derivable prefixes by
|
||||
// default.
|
||||
NoncePrefix string `validate:"excluded_with=UseDerivablePrefix,omitempty,len=4"`
|
||||
|
||||
// UseDerivablePrefix indicates whether to use a nonce prefix derived
|
||||
// from the gRPC listening address. If this is false, the nonce prefix
|
||||
// will be the value of the NoncePrefix field. If this is true, the
|
||||
// NoncePrefixKey field is required.
|
||||
// TODO(#6610): Remove this.
|
||||
//
|
||||
// TODO(#6610): Remove once we've moved to derivable prefixes by
|
||||
// default.
|
||||
UseDerivablePrefix bool `validate:"excluded_with=NoncePrefix"`
|
||||
// Deprecated: this value is ignored, and treated as though it is always true.
|
||||
UseDerivablePrefix bool `validate:"-"`
|
||||
|
||||
// NoncePrefixKey is a secret used for deriving the prefix of each nonce
|
||||
// instance. It should contain 256 bits (32 bytes) of random data to be
|
||||
// suitable as an HMAC-SHA256 key (e.g. the output of `openssl rand -hex
|
||||
// 32`). In a multi-DC deployment this value should be the same across
|
||||
// all boulder-wfe and nonce-service instances. This is only used if
|
||||
// UseDerivablePrefix is true.
|
||||
//
|
||||
// TODO(#6610): Edit this comment once we've moved to derivable prefixes
|
||||
// by default.
|
||||
NoncePrefixKey cmd.PasswordConfig `validate:"excluded_with=NoncePrefix,structonly"`
|
||||
// all boulder-wfe and nonce-service instances.
|
||||
NoncePrefixKey cmd.PasswordConfig `validate:"required"`
|
||||
|
||||
Syslog cmd.SyslogConfig
|
||||
OpenTelemetry cmd.OpenTelemetryConfig
|
||||
|
|
@ -89,28 +82,20 @@ func main() {
|
|||
c.NonceService.DebugAddr = *debugAddr
|
||||
}
|
||||
|
||||
// TODO(#6610): Remove once we've moved to derivable prefixes by default.
|
||||
if c.NonceService.NoncePrefix != "" && c.NonceService.UseDerivablePrefix {
|
||||
cmd.Fail("Cannot set both 'noncePrefix' and 'useDerivablePrefix'")
|
||||
if c.NonceService.NoncePrefixKey.PasswordFile == "" {
|
||||
cmd.Fail("NoncePrefixKey PasswordFile must be set")
|
||||
}
|
||||
|
||||
// TODO(#6610): Remove once we've moved to derivable prefixes by default.
|
||||
if c.NonceService.UseDerivablePrefix && c.NonceService.NoncePrefixKey.PasswordFile == "" {
|
||||
cmd.Fail("Cannot set 'noncePrefixKey' without 'useDerivablePrefix'")
|
||||
}
|
||||
|
||||
if c.NonceService.UseDerivablePrefix && c.NonceService.NoncePrefixKey.PasswordFile != "" {
|
||||
key, err := c.NonceService.NoncePrefixKey.Pass()
|
||||
cmd.FailOnError(err, "Failed to load 'noncePrefixKey' file.")
|
||||
c.NonceService.NoncePrefix, err = derivePrefix(key, c.NonceService.GRPC.Address)
|
||||
cmd.FailOnError(err, "Failed to derive nonce prefix")
|
||||
}
|
||||
key, err := c.NonceService.NoncePrefixKey.Pass()
|
||||
cmd.FailOnError(err, "Failed to load 'noncePrefixKey' file.")
|
||||
noncePrefix, err := derivePrefix(key, c.NonceService.GRPC.Address)
|
||||
cmd.FailOnError(err, "Failed to derive nonce prefix")
|
||||
|
||||
scope, logger, oTelShutdown := cmd.StatsAndLogging(c.NonceService.Syslog, c.NonceService.OpenTelemetry, c.NonceService.DebugAddr)
|
||||
defer oTelShutdown(context.Background())
|
||||
logger.Info(cmd.VersionString())
|
||||
|
||||
ns, err := nonce.NewNonceService(scope, c.NonceService.MaxUsed, c.NonceService.NoncePrefix)
|
||||
ns, err := nonce.NewNonceService(scope, c.NonceService.MaxUsed, noncePrefix)
|
||||
cmd.FailOnError(err, "Failed to initialize nonce service")
|
||||
|
||||
tlsConfig, err := c.NonceService.TLS.Load(scope)
|
||||
|
|
|
|||
|
|
@ -39,12 +39,6 @@ const (
|
|||
// PrefixLen is the character length of a nonce prefix.
|
||||
PrefixLen = 8
|
||||
|
||||
// DeprecatedPrefixLen is the character length of a nonce prefix.
|
||||
//
|
||||
// Deprecated: Use PrefixLen instead.
|
||||
// TODO(#6610): Remove once we've moved to derivable prefixes by default.
|
||||
DeprecatedPrefixLen = 4
|
||||
|
||||
// NonceLen is the character length of a nonce, excluding the prefix.
|
||||
NonceLen = 32
|
||||
defaultMaxUsed = 65536
|
||||
|
|
@ -81,9 +75,6 @@ type NonceService struct {
|
|||
nonceEarliest prometheus.Gauge
|
||||
nonceRedeems *prometheus.CounterVec
|
||||
nonceHeapLatency prometheus.Histogram
|
||||
// TODO(#6610): Remove this field once we've moved to derivable prefixes by
|
||||
// default.
|
||||
prefixLen int
|
||||
}
|
||||
|
||||
type int64Heap []int64
|
||||
|
|
@ -106,23 +97,16 @@ func (h *int64Heap) Pop() interface{} {
|
|||
|
||||
// NewNonceService constructs a NonceService with defaults
|
||||
func NewNonceService(stats prometheus.Registerer, maxUsed int, prefix string) (*NonceService, error) {
|
||||
// If a prefix is provided it must be four characters and valid base64. The
|
||||
// If a prefix is provided it must be eight characters and valid base64. The
|
||||
// prefix is required to be base64url as RFC8555 section 6.5.1 requires that
|
||||
// nonces use that encoding. As base64 operates on three byte binary
|
||||
// segments we require the prefix to be three or six bytes (four or eight
|
||||
// characters) so that the bytes preceding the prefix wouldn't impact the
|
||||
// encoding.
|
||||
//
|
||||
// TODO(#6610): Update this comment once we've moved to eight character
|
||||
// prefixes by default.
|
||||
// nonces use that encoding. As base64 operates on three byte binary segments
|
||||
// we require the prefix to be six bytes (eight characters) so that the bytes
|
||||
// preceding the prefix wouldn't impact the encoding.
|
||||
if prefix != "" {
|
||||
// TODO(#6610): Refactor once we've moved to derivable prefixes by
|
||||
// default.
|
||||
if len(prefix) != PrefixLen && len(prefix) != DeprecatedPrefixLen {
|
||||
if len(prefix) != PrefixLen {
|
||||
return nil, fmt.Errorf(
|
||||
"'noncePrefix' must be %d or %d characters, not %d",
|
||||
"nonce prefix must be %d characters, not %d",
|
||||
PrefixLen,
|
||||
DeprecatedPrefixLen,
|
||||
len(prefix),
|
||||
)
|
||||
}
|
||||
|
|
@ -182,9 +166,6 @@ func NewNonceService(stats prometheus.Registerer, maxUsed int, prefix string) (*
|
|||
nonceEarliest: nonceEarliest,
|
||||
nonceRedeems: nonceRedeems,
|
||||
nonceHeapLatency: nonceHeapLatency,
|
||||
// TODO(#6610): Remove this field once we've moved to derivable prefixes
|
||||
// by default.
|
||||
prefixLen: len(prefix),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -303,43 +284,10 @@ func (ns *NonceService) Valid(nonce string) bool {
|
|||
|
||||
// splitNonce splits a nonce into a prefix and a body.
|
||||
func (ns *NonceService) splitNonce(nonce string) (string, string, error) {
|
||||
if len(nonce) < ns.prefixLen {
|
||||
if len(nonce) < PrefixLen {
|
||||
return "", "", errInvalidNonceLength
|
||||
}
|
||||
return nonce[:ns.prefixLen], nonce[ns.prefixLen:], nil
|
||||
}
|
||||
|
||||
// splitDeprecatedNonce splits a nonce into a prefix and a body.
|
||||
//
|
||||
// Deprecated: Use NonceService.splitDeprecatedNonce instead.
|
||||
// TODO(#6610): Remove this function once we've moved to derivable prefixes by
|
||||
// default.
|
||||
func splitDeprecatedNonce(nonce string) (string, string, error) {
|
||||
if len(nonce) < DeprecatedPrefixLen {
|
||||
return "", "", errInvalidNonceLength
|
||||
}
|
||||
return nonce[:DeprecatedPrefixLen], nonce[DeprecatedPrefixLen:], nil
|
||||
}
|
||||
|
||||
// RemoteRedeem checks the nonce prefix and routes the Redeem RPC
|
||||
// to the associated remote nonce service.
|
||||
//
|
||||
// TODO(#6610): Remove this function once we've moved to derivable prefixes by
|
||||
// default.
|
||||
func RemoteRedeem(ctx context.Context, noncePrefixMap map[string]Redeemer, nonce string) (bool, error) {
|
||||
prefix, _, err := splitDeprecatedNonce(nonce)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
nonceService, present := noncePrefixMap[prefix]
|
||||
if !present {
|
||||
return false, nil
|
||||
}
|
||||
resp, err := nonceService.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return resp.Valid, nil
|
||||
return nonce[:PrefixLen], nonce[PrefixLen:], nil
|
||||
}
|
||||
|
||||
// NewServer returns a new Server, wrapping a NonceService.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
package nonce
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
noncepb "github.com/letsencrypt/boulder/nonce/proto"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
|
|
@ -129,7 +125,7 @@ func BenchmarkNonces(b *testing.B) {
|
|||
}
|
||||
|
||||
func TestNoncePrefixing(t *testing.T) {
|
||||
ns, err := NewNonceService(metrics.NoopRegisterer, 0, "zinc")
|
||||
ns, err := NewNonceService(metrics.NoopRegisterer, 0, "aluminum")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
|
||||
n, err := ns.Nonce()
|
||||
|
|
@ -146,66 +142,12 @@ func TestNoncePrefixing(t *testing.T) {
|
|||
test.Assert(t, !ns.Valid(n[6:]), "Valid nonce without prefix accepted")
|
||||
}
|
||||
|
||||
type validRedeemer struct{}
|
||||
|
||||
func (vr *validRedeemer) Redeem(ctx context.Context, in *noncepb.NonceMessage, _ ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return &noncepb.ValidMessage{Valid: true}, nil
|
||||
}
|
||||
|
||||
type invalidRedeemer struct{}
|
||||
|
||||
func (ivr *invalidRedeemer) Redeem(ctx context.Context, in *noncepb.NonceMessage, _ ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return &noncepb.ValidMessage{Valid: false}, nil
|
||||
}
|
||||
|
||||
type brokenRedeemer struct{}
|
||||
|
||||
func (br *brokenRedeemer) Redeem(ctx context.Context, in *noncepb.NonceMessage, _ ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return nil, errors.New("broken redeemer!")
|
||||
}
|
||||
|
||||
func TestRemoteRedeem(t *testing.T) {
|
||||
valid, err := RemoteRedeem(context.Background(), nil, "q")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, !valid, "RemoteRedeem accepted an invalid nonce")
|
||||
valid, err = RemoteRedeem(context.Background(), nil, "")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, !valid, "RemoteRedeem accepted an empty nonce")
|
||||
|
||||
prefixMap := map[string]Redeemer{
|
||||
"abcd": &brokenRedeemer{},
|
||||
"wxyz": &invalidRedeemer{},
|
||||
}
|
||||
// Attempt to redeem a nonce with a prefix not in the prefix map, expect return false, nil
|
||||
valid, err = RemoteRedeem(context.Background(), prefixMap, "asddCQEC")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, !valid, "RemoteRedeem accepted nonce not in prefix map")
|
||||
|
||||
// Attempt to redeem a nonce with a prefix in the prefix map, remote returns error
|
||||
// expect false, err
|
||||
_, err = RemoteRedeem(context.Background(), prefixMap, "abcdbeef")
|
||||
test.AssertError(t, err, "RemoteRedeem didn't return error when remote did")
|
||||
|
||||
// Attempt to redeem a nonce with a prefix in the prefix map, remote returns invalid
|
||||
// expect false, nil
|
||||
valid, err = RemoteRedeem(context.Background(), prefixMap, "wxyzdead")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, !valid, "RemoteRedeem didn't honor remote result")
|
||||
|
||||
// Attempt to redeem a nonce with a prefix in the prefix map, remote returns valid
|
||||
// expect true, nil
|
||||
prefixMap["wxyz"] = &validRedeemer{}
|
||||
valid, err = RemoteRedeem(context.Background(), prefixMap, "wxyzdead")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, valid, "RemoteRedeem didn't honor remote result")
|
||||
}
|
||||
|
||||
func TestNoncePrefixValidation(t *testing.T) {
|
||||
_, err := NewNonceService(metrics.NoopRegisterer, 0, "hey")
|
||||
_, err := NewNonceService(metrics.NoopRegisterer, 0, "whatsup")
|
||||
test.AssertError(t, err, "NewNonceService didn't fail with short prefix")
|
||||
_, err = NewNonceService(metrics.NoopRegisterer, 0, "hey!")
|
||||
_, err = NewNonceService(metrics.NoopRegisterer, 0, "whatsup!")
|
||||
test.AssertError(t, err, "NewNonceService didn't fail with invalid base64")
|
||||
_, err = NewNonceService(metrics.NoopRegisterer, 0, "heyy")
|
||||
_, err = NewNonceService(metrics.NoopRegisterer, 0, "whatsupp")
|
||||
test.AssertNotError(t, err, "NewNonceService failed with valid nonce prefix")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"NonceService": {
|
||||
"maxUsed": 131072,
|
||||
"useDerivablePrefix": true,
|
||||
"noncePrefixKey": {
|
||||
"passwordFile": "test/secrets/nonce_prefix_key"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"NonceService": {
|
||||
"maxUsed": 131072,
|
||||
"useDerivablePrefix": true,
|
||||
"noncePrefixKey": {
|
||||
"passwordFile": "test/secrets/nonce_prefix_key"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -224,53 +224,35 @@ func (wfe *WebFrontEndImpl) validNonce(ctx context.Context, header jose.Header)
|
|||
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMissingNonce"}).Inc()
|
||||
return probs.BadNonce("JWS has no anti-replay nonce")
|
||||
}
|
||||
var valid bool
|
||||
var err error
|
||||
if wfe.noncePrefixMap == nil {
|
||||
// Dispatch nonce redemption RPCs dynamically.
|
||||
prob := nonceWellFormed(header.Nonce, nonce.PrefixLen)
|
||||
if prob != nil {
|
||||
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMalformedNonce"}).Inc()
|
||||
return prob
|
||||
}
|
||||
|
||||
// Populate the context with the nonce prefix and HMAC key. These are
|
||||
// used by a custom gRPC balancer, known as "noncebalancer", to route
|
||||
// redemption RPCs to the backend that originally issued the nonce.
|
||||
ctx = context.WithValue(ctx, nonce.PrefixCtxKey{}, header.Nonce[:nonce.PrefixLen])
|
||||
ctx = context.WithValue(ctx, nonce.HMACKeyCtxKey{}, wfe.rncKey)
|
||||
prob := nonceWellFormed(header.Nonce, nonce.PrefixLen)
|
||||
if prob != nil {
|
||||
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMalformedNonce"}).Inc()
|
||||
return prob
|
||||
}
|
||||
|
||||
resp, err := wfe.rnc.Redeem(ctx, &noncepb.NonceMessage{Nonce: header.Nonce})
|
||||
if err != nil {
|
||||
rpcStatus, ok := status.FromError(err)
|
||||
if !ok || rpcStatus != nb.ErrNoBackendsMatchPrefix {
|
||||
return web.ProblemDetailsForError(err, "failed to redeem nonce")
|
||||
}
|
||||
// Populate the context with the nonce prefix and HMAC key. These are
|
||||
// used by a custom gRPC balancer, known as "noncebalancer", to route
|
||||
// redemption RPCs to the backend that originally issued the nonce.
|
||||
ctx = context.WithValue(ctx, nonce.PrefixCtxKey{}, header.Nonce[:nonce.PrefixLen])
|
||||
ctx = context.WithValue(ctx, nonce.HMACKeyCtxKey{}, wfe.rncKey)
|
||||
|
||||
// ErrNoBackendsMatchPrefix suggests that the nonce backend, which
|
||||
// issued this nonce, is presently unreachable or unrecognized by
|
||||
// this WFE. As this is a transient failure, the client should retry
|
||||
// their request with a fresh nonce.
|
||||
resp = &noncepb.ValidMessage{Valid: false}
|
||||
wfe.stats.nonceNoMatchingBackendCount.Inc()
|
||||
}
|
||||
valid = resp.Valid
|
||||
} else {
|
||||
// Dispatch nonce redpemption RPCs using a static mapping.
|
||||
//
|
||||
// TODO(#6610) Remove code below and the `npm` mapping.
|
||||
prob := nonceWellFormed(header.Nonce, nonce.DeprecatedPrefixLen)
|
||||
if prob != nil {
|
||||
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMalformedNonce"}).Inc()
|
||||
return prob
|
||||
}
|
||||
|
||||
valid, err = nonce.RemoteRedeem(ctx, wfe.noncePrefixMap, header.Nonce)
|
||||
if err != nil {
|
||||
resp, err := wfe.rnc.Redeem(ctx, &noncepb.NonceMessage{Nonce: header.Nonce})
|
||||
if err != nil {
|
||||
rpcStatus, ok := status.FromError(err)
|
||||
if !ok || rpcStatus != nb.ErrNoBackendsMatchPrefix {
|
||||
return web.ProblemDetailsForError(err, "failed to redeem nonce")
|
||||
}
|
||||
|
||||
// ErrNoBackendsMatchPrefix suggests that the nonce backend, which
|
||||
// issued this nonce, is presently unreachable or unrecognized by
|
||||
// this WFE. As this is a transient failure, the client should retry
|
||||
// their request with a fresh nonce.
|
||||
resp = &noncepb.ValidMessage{Valid: false}
|
||||
wfe.stats.nonceNoMatchingBackendCount.Inc()
|
||||
}
|
||||
if !valid {
|
||||
|
||||
if !resp.Valid {
|
||||
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSInvalidNonce"}).Inc()
|
||||
return probs.BadNonce(fmt.Sprintf("JWS has an invalid anti-replay nonce: %q", header.Nonce))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"crypto/rsa"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -691,10 +690,6 @@ func (b badNonceProvider) Nonce() (string, error) {
|
|||
// characters and static prefixes are 4 characters.
|
||||
return "woww", nil
|
||||
}
|
||||
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
||||
// TODO(#6610): Remove this.
|
||||
return "mlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA", nil
|
||||
}
|
||||
return "mlolmlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA", nil
|
||||
}
|
||||
|
||||
|
|
@ -708,10 +703,6 @@ func TestValidNonce(t *testing.T) {
|
|||
JWS *jose.JSONWebSignature
|
||||
ExpectedResult *probs.ProblemDetails
|
||||
ErrorStatType string
|
||||
// TODO(#6610): Remove this.
|
||||
SkipConfigNext bool
|
||||
// TODO(#6610): Remove this.
|
||||
SkipConfig bool
|
||||
}{
|
||||
{
|
||||
Name: "No nonce in JWS",
|
||||
|
|
@ -743,17 +734,6 @@ func TestValidNonce(t *testing.T) {
|
|||
},
|
||||
ErrorStatType: "JWSMalformedNonce",
|
||||
},
|
||||
{
|
||||
Name: "Invalid nonce in JWS (test/config)",
|
||||
JWS: signer.invalidNonce(),
|
||||
ExpectedResult: &probs.ProblemDetails{
|
||||
Type: probs.BadNonceProblem,
|
||||
Detail: "JWS has an invalid anti-replay nonce: \"mlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA\"",
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
},
|
||||
ErrorStatType: "JWSInvalidNonce",
|
||||
SkipConfigNext: true,
|
||||
},
|
||||
{
|
||||
Name: "Invalid nonce in JWS (test/config-next)",
|
||||
JWS: signer.invalidNonce(),
|
||||
|
|
@ -763,7 +743,6 @@ func TestValidNonce(t *testing.T) {
|
|||
HTTPStatus: http.StatusBadRequest,
|
||||
},
|
||||
ErrorStatType: "JWSInvalidNonce",
|
||||
SkipConfig: true,
|
||||
},
|
||||
{
|
||||
Name: "Valid nonce in JWS",
|
||||
|
|
@ -774,14 +753,6 @@ func TestValidNonce(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
// TODO(#6610): Remove this.
|
||||
if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" {
|
||||
if tc.SkipConfigNext {
|
||||
t.Skip("Skipping test in config-next")
|
||||
}
|
||||
} else if tc.SkipConfig {
|
||||
t.Skip("Skipping test in config")
|
||||
}
|
||||
wfe.stats.joseErrorCount.Reset()
|
||||
prob := wfe.validNonce(context.Background(), tc.JWS.Signatures[0].Header)
|
||||
if tc.ExpectedResult == nil && prob != nil {
|
||||
|
|
@ -806,10 +777,6 @@ func (n noBackendsNonceRedeemer) Redeem(ctx context.Context, _ *noncepb.NonceMes
|
|||
}
|
||||
|
||||
func TestValidNonce_NoMatchingBackendFound(t *testing.T) {
|
||||
// TODO(#6610): Remove this.
|
||||
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
||||
t.Skip("Skipping test in config")
|
||||
}
|
||||
wfe, _, signer := setupWFE(t)
|
||||
goodJWS, _, _ := signer.embeddedJWK(nil, "", "")
|
||||
wfe.rnc = noBackendsNonceRedeemer{}
|
||||
|
|
@ -1364,10 +1331,6 @@ func TestValidJWSForKey(t *testing.T) {
|
|||
Body string
|
||||
ExpectedProblem *probs.ProblemDetails
|
||||
ErrorStatType string
|
||||
// TODO(#6610): Remove this.
|
||||
SkipConfig bool
|
||||
// TODO(#6610): Remove this.
|
||||
SkipConfigNext bool
|
||||
}{
|
||||
{
|
||||
Name: "JWS with an invalid algorithm",
|
||||
|
|
@ -1380,18 +1343,6 @@ func TestValidJWSForKey(t *testing.T) {
|
|||
},
|
||||
ErrorStatType: "JWSAlgorithmCheckFailed",
|
||||
},
|
||||
{
|
||||
Name: "JWS with an invalid nonce (test/config)",
|
||||
JWS: bJSONWebSignature{signer.invalidNonce()},
|
||||
JWK: goodJWK,
|
||||
ExpectedProblem: &probs.ProblemDetails{
|
||||
Type: probs.BadNonceProblem,
|
||||
Detail: "JWS has an invalid anti-replay nonce: \"mlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA\"",
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
},
|
||||
ErrorStatType: "JWSInvalidNonce",
|
||||
SkipConfigNext: true,
|
||||
},
|
||||
{
|
||||
Name: "JWS with an invalid nonce (test/config-next)",
|
||||
JWS: bJSONWebSignature{signer.invalidNonce()},
|
||||
|
|
@ -1402,7 +1353,6 @@ func TestValidJWSForKey(t *testing.T) {
|
|||
HTTPStatus: http.StatusBadRequest,
|
||||
},
|
||||
ErrorStatType: "JWSInvalidNonce",
|
||||
SkipConfig: true,
|
||||
},
|
||||
{
|
||||
Name: "JWS with broken signature",
|
||||
|
|
@ -1445,16 +1395,7 @@ func TestValidJWSForKey(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// TODO(#6610): Remove this.
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
// TODO(#6610): Remove this.
|
||||
if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" {
|
||||
if tc.SkipConfigNext {
|
||||
t.Skip("Skipping test in config-next")
|
||||
}
|
||||
} else if tc.SkipConfig {
|
||||
t.Skip("Skipping test in config")
|
||||
}
|
||||
wfe.stats.joseErrorCount.Reset()
|
||||
request := makePostRequestWithPath("test", tc.Body)
|
||||
outPayload, prob := wfe.validJWSForKey(context.Background(), &tc.JWS, tc.JWK, request)
|
||||
|
|
|
|||
|
|
@ -97,10 +97,6 @@ type WebFrontEndImpl struct {
|
|||
// nonces. It's configured to route requests to backends colocated with the
|
||||
// WFE.
|
||||
gnc nonce.Getter
|
||||
// Register of anti-replay nonces
|
||||
//
|
||||
// Deprecated: See `rnc`, above.
|
||||
noncePrefixMap map[string]nonce.Redeemer
|
||||
// rnc is a nonce-service client used exclusively for the redemption of
|
||||
// nonces. It uses a custom RPC load balancer which is configured to route
|
||||
// requests to backends based on the prefix and HMAC key passed as in the
|
||||
|
|
@ -189,7 +185,6 @@ func NewWebFrontEndImpl(
|
|||
rac rapb.RegistrationAuthorityClient,
|
||||
sac sapb.StorageAuthorityReadOnlyClient,
|
||||
gnc nonce.Getter,
|
||||
noncePrefixMap map[string]nonce.Redeemer,
|
||||
rnc nonce.Redeemer,
|
||||
rncKey string,
|
||||
accountGetter AccountGetter,
|
||||
|
|
@ -210,8 +205,7 @@ func NewWebFrontEndImpl(
|
|||
return WebFrontEndImpl{}, errors.New("must provide a service for nonce issuance")
|
||||
}
|
||||
|
||||
// TODO(#6610): Remove the check for the map.
|
||||
if noncePrefixMap == nil && rnc == nil {
|
||||
if rnc == nil {
|
||||
return WebFrontEndImpl{}, errors.New("must provide a service for nonce redemption")
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +223,6 @@ func NewWebFrontEndImpl(
|
|||
ra: rac,
|
||||
sa: sac,
|
||||
gnc: gnc,
|
||||
noncePrefixMap: noncePrefixMap,
|
||||
rnc: rnc,
|
||||
rncKey: rncKey,
|
||||
accountGetter: accountGetter,
|
||||
|
|
|
|||
|
|
@ -349,61 +349,42 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
|
|||
|
||||
log := blog.NewMock()
|
||||
|
||||
var gnc nonce.Getter
|
||||
var noncePrefixMap map[string]nonce.Redeemer
|
||||
var rnc nonce.Redeemer
|
||||
var rncKey string
|
||||
var inmemNonceService *inmemnonce.Service
|
||||
var limiter *ratelimits.Limiter
|
||||
var txnBuilder *ratelimits.TransactionBuilder
|
||||
if strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
|
||||
// Use derived nonces.
|
||||
noncePrefix := nonce.DerivePrefix("192.168.1.1:8080", "b8c758dd85e113ea340ce0b3a99f389d40a308548af94d1730a7692c1874f1f")
|
||||
nonceService, err := nonce.NewNonceService(metrics.NoopRegisterer, 100, noncePrefix)
|
||||
test.AssertNotError(t, err, "making nonceService")
|
||||
// Use derived nonces.
|
||||
noncePrefix := nonce.DerivePrefix("192.168.1.1:8080", "b8c758dd85e113ea340ce0b3a99f389d40a308548af94d1730a7692c1874f1f")
|
||||
nonceService, err := nonce.NewNonceService(metrics.NoopRegisterer, 100, noncePrefix)
|
||||
test.AssertNotError(t, err, "making nonceService")
|
||||
|
||||
inmemNonceService = &inmemnonce.Service{NonceService: nonceService}
|
||||
gnc = inmemNonceService
|
||||
rnc = inmemNonceService
|
||||
inmemNonceService := &inmemnonce.Service{NonceService: nonceService}
|
||||
gnc := inmemNonceService
|
||||
rnc := inmemNonceService
|
||||
|
||||
// Setup rate limiting.
|
||||
rc := bredis.Config{
|
||||
Username: "unittest-rw",
|
||||
TLS: cmd.TLSConfig{
|
||||
CACertFile: "../test/certs/ipki/minica.pem",
|
||||
CertFile: "../test/certs/ipki/localhost/cert.pem",
|
||||
KeyFile: "../test/certs/ipki/localhost/key.pem",
|
||||
// Setup rate limiting.
|
||||
rc := bredis.Config{
|
||||
Username: "unittest-rw",
|
||||
TLS: cmd.TLSConfig{
|
||||
CACertFile: "../test/certs/ipki/minica.pem",
|
||||
CertFile: "../test/certs/ipki/localhost/cert.pem",
|
||||
KeyFile: "../test/certs/ipki/localhost/key.pem",
|
||||
},
|
||||
Lookups: []cmd.ServiceDomain{
|
||||
{
|
||||
Service: "redisratelimits",
|
||||
Domain: "service.consul",
|
||||
},
|
||||
Lookups: []cmd.ServiceDomain{
|
||||
{
|
||||
Service: "redisratelimits",
|
||||
Domain: "service.consul",
|
||||
},
|
||||
},
|
||||
LookupDNSAuthority: "consul.service.consul",
|
||||
}
|
||||
rc.PasswordConfig = cmd.PasswordConfig{
|
||||
PasswordFile: "../test/secrets/ratelimits_redis_password",
|
||||
}
|
||||
ring, err := bredis.NewRingFromConfig(rc, stats, log)
|
||||
test.AssertNotError(t, err, "making redis ring client")
|
||||
source := ratelimits.NewRedisSource(ring.Ring, fc, stats)
|
||||
test.AssertNotNil(t, source, "source should not be nil")
|
||||
limiter, err = ratelimits.NewLimiter(fc, source, stats)
|
||||
test.AssertNotError(t, err, "making limiter")
|
||||
txnBuilder, err = ratelimits.NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "")
|
||||
test.AssertNotError(t, err, "making transaction composer")
|
||||
} else {
|
||||
// TODO(#6610): Remove this once we've moved to derived to prefixes.
|
||||
noncePrefix := "mlem"
|
||||
nonceService, err := nonce.NewNonceService(metrics.NoopRegisterer, 100, noncePrefix)
|
||||
test.AssertNotError(t, err, "making nonceService")
|
||||
|
||||
inmemNonceService = &inmemnonce.Service{NonceService: nonceService}
|
||||
gnc = inmemNonceService
|
||||
noncePrefixMap = map[string]nonce.Redeemer{noncePrefix: inmemNonceService}
|
||||
rnc = inmemNonceService
|
||||
},
|
||||
LookupDNSAuthority: "consul.service.consul",
|
||||
}
|
||||
rc.PasswordConfig = cmd.PasswordConfig{
|
||||
PasswordFile: "../test/secrets/ratelimits_redis_password",
|
||||
}
|
||||
ring, err := bredis.NewRingFromConfig(rc, stats, log)
|
||||
test.AssertNotError(t, err, "making redis ring client")
|
||||
source := ratelimits.NewRedisSource(ring.Ring, fc, stats)
|
||||
test.AssertNotNil(t, source, "source should not be nil")
|
||||
limiter, err := ratelimits.NewLimiter(fc, source, stats)
|
||||
test.AssertNotError(t, err, "making limiter")
|
||||
txnBuilder, err := ratelimits.NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "")
|
||||
test.AssertNotError(t, err, "making transaction composer")
|
||||
|
||||
wfe, err := NewWebFrontEndImpl(
|
||||
stats,
|
||||
|
|
@ -419,9 +400,8 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
|
|||
&MockRegistrationAuthority{},
|
||||
mockSA,
|
||||
gnc,
|
||||
noncePrefixMap,
|
||||
rnc,
|
||||
rncKey,
|
||||
"rncKey",
|
||||
mockSA,
|
||||
limiter,
|
||||
txnBuilder,
|
||||
|
|
|
|||
Loading…
Reference in New Issue