Allow forwarding of nonce-service Redeem RPCs from one service… (#4297)
Fixes #4295.
This commit is contained in:
parent
352899ba2f
commit
844ae26b65
|
@ -4,10 +4,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/nonce"
|
||||
noncepb "github.com/letsencrypt/boulder/nonce/proto"
|
||||
)
|
||||
|
@ -17,18 +20,69 @@ type config struct {
|
|||
cmd.ServiceConfig
|
||||
Syslog cmd.SyslogConfig
|
||||
MaxUsed int
|
||||
|
||||
RemoteNonceServices []cmd.GRPCClientConfig
|
||||
}
|
||||
}
|
||||
|
||||
type nonceServer struct {
|
||||
inner *nonce.NonceService
|
||||
inner *nonce.NonceService
|
||||
remoteServices []noncepb.NonceServiceClient
|
||||
log blog.Logger
|
||||
}
|
||||
|
||||
func (ns *nonceServer) Redeem(_ context.Context, msg *noncepb.NonceMessage) (*noncepb.ValidMessage, error) {
|
||||
func (ns *nonceServer) remoteRedeem(ctx context.Context, msg *noncepb.NonceMessage) bool {
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
ns.log.Err("Context passed to remoteRedeem does not have a deadline")
|
||||
return false
|
||||
}
|
||||
subCtx, cancel := context.WithDeadline(ctx, deadline.Add(-time.Millisecond*250))
|
||||
defer cancel()
|
||||
forwarded := true
|
||||
msg.Forwarded = &forwarded
|
||||
results := make(chan bool, len(ns.remoteServices))
|
||||
wg := new(sync.WaitGroup)
|
||||
for _, remote := range ns.remoteServices {
|
||||
wg.Add(1)
|
||||
go func(r noncepb.NonceServiceClient) {
|
||||
defer wg.Done()
|
||||
resp, err := r.Redeem(subCtx, msg)
|
||||
if err != nil {
|
||||
ns.log.Errf("remote Redeem call failed: %s", err)
|
||||
return
|
||||
}
|
||||
results <- *resp.Valid
|
||||
}(remote)
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
for result := range results {
|
||||
select {
|
||||
case <-subCtx.Done():
|
||||
return false
|
||||
default:
|
||||
if result {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ns *nonceServer) Redeem(ctx context.Context, msg *noncepb.NonceMessage) (*noncepb.ValidMessage, error) {
|
||||
if msg.Nonce == nil {
|
||||
return nil, errors.New("Incomplete gRPC request message")
|
||||
}
|
||||
valid := ns.inner.Valid(*msg.Nonce)
|
||||
// If the nonce was not valid, we have configured remote nonce services,
|
||||
// and this Redeem message wasn't forwarded, then forward it to the
|
||||
// remote services
|
||||
if !valid && len(ns.remoteServices) > 0 && msg.Forwarded != nil && !*msg.Forwarded {
|
||||
valid = ns.remoteRedeem(ctx, msg)
|
||||
}
|
||||
return &noncepb.ValidMessage{Valid: &valid}, nil
|
||||
}
|
||||
|
||||
|
@ -41,6 +95,8 @@ func (ns *nonceServer) Nonce(_ context.Context, _ *corepb.Empty) (*noncepb.Nonce
|
|||
}
|
||||
|
||||
func main() {
|
||||
grpcAddr := flag.String("addr", "", "gRPC listen address override")
|
||||
debugAddr := flag.String("debug-addr", "", "Debug server address override")
|
||||
configFile := flag.String("config", "", "File path to the configuration file for this service")
|
||||
flag.Parse()
|
||||
|
||||
|
@ -48,6 +104,13 @@ func main() {
|
|||
err := cmd.ReadConfigFile(*configFile, &c)
|
||||
cmd.FailOnError(err, "Reading JSON config file into config structure")
|
||||
|
||||
if *grpcAddr != "" {
|
||||
c.NonceService.GRPC.Address = *grpcAddr
|
||||
}
|
||||
if *debugAddr != "" {
|
||||
c.NonceService.DebugAddr = *debugAddr
|
||||
}
|
||||
|
||||
scope, logger := cmd.StatsAndLogging(c.NonceService.Syslog, c.NonceService.DebugAddr)
|
||||
defer logger.AuditPanic()
|
||||
logger.Info(cmd.VersionString())
|
||||
|
@ -57,10 +120,22 @@ func main() {
|
|||
|
||||
tlsConfig, err := c.NonceService.TLS.Load()
|
||||
cmd.FailOnError(err, "tlsConfig config")
|
||||
|
||||
nonceServer := &nonceServer{inner: ns, log: logger}
|
||||
if len(c.NonceService.RemoteNonceServices) > 0 {
|
||||
clientMetrics := bgrpc.NewClientMetrics(scope)
|
||||
clk := cmd.Clock()
|
||||
for _, remoteNonceConfig := range c.NonceService.RemoteNonceServices {
|
||||
rnsConn, err := bgrpc.ClientSetup(&remoteNonceConfig, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to Nonce service")
|
||||
nonceServer.remoteServices = append(nonceServer.remoteServices, noncepb.NewNonceServiceClient(rnsConn))
|
||||
}
|
||||
}
|
||||
|
||||
serverMetrics := bgrpc.NewServerMetrics(scope)
|
||||
grpcSrv, l, err := bgrpc.NewServer(c.NonceService.GRPC, tlsConfig, serverMetrics, cmd.Clock())
|
||||
cmd.FailOnError(err, "Unable to setup nonce service gRPC server")
|
||||
noncepb.RegisterNonceServiceServer(grpcSrv, &nonceServer{inner: ns})
|
||||
noncepb.RegisterNonceServiceServer(grpcSrv, nonceServer)
|
||||
|
||||
go cmd.CatchSignals(logger, grpcSrv.GracefulStop)
|
||||
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/nonce"
|
||||
noncepb "github.com/letsencrypt/boulder/nonce/proto"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type workingRemote struct{ resp bool }
|
||||
|
||||
func (wr *workingRemote) Redeem(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return &noncepb.ValidMessage{
|
||||
Valid: &wr.resp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (wr *workingRemote) Nonce(ctx context.Context, in *corepb.Empty, opts ...grpc.CallOption) (*noncepb.NonceMessage, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type sleepingRemote struct{}
|
||||
|
||||
func (sr *sleepingRemote) Redeem(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
valid := true
|
||||
return &noncepb.ValidMessage{
|
||||
Valid: &valid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (sr *sleepingRemote) Nonce(ctx context.Context, in *corepb.Empty, opts ...grpc.CallOption) (*noncepb.NonceMessage, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type brokenRemote struct{}
|
||||
|
||||
func (br *brokenRemote) Redeem(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return nil, errors.New("BROKE!")
|
||||
}
|
||||
|
||||
func (br *brokenRemote) Nonce(ctx context.Context, in *corepb.Empty, opts ...grpc.CallOption) (*noncepb.NonceMessage, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestRemoteRedeem(t *testing.T) {
|
||||
l := blog.NewMock()
|
||||
|
||||
innerNs, err := nonce.NewNonceService(metrics.NewNoopScope(), 1)
|
||||
test.AssertNotError(t, err, "NewNonceService failed")
|
||||
ns := nonceServer{log: l, inner: innerNs}
|
||||
|
||||
// Working remote returning valid nonce message
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&workingRemote{resp: false},
|
||||
&workingRemote{resp: true},
|
||||
}
|
||||
nonce := "asd"
|
||||
forwarded := false
|
||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Hour))
|
||||
resp, err := ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: &nonce, Forwarded: &forwarded})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, *resp.Valid, "Redeem returned the wrong response")
|
||||
|
||||
// Working remotes returning invalid nonce message
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&workingRemote{resp: false},
|
||||
&workingRemote{resp: false},
|
||||
}
|
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Hour))
|
||||
resp, err = ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: &nonce, Forwarded: &forwarded})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, !*resp.Valid, "Redeem returned the wrong response")
|
||||
|
||||
// Sleeping remotes returns valid nonce message, but after 50ms, Redeem should return false
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&sleepingRemote{},
|
||||
&sleepingRemote{},
|
||||
}
|
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond))
|
||||
resp, err = ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: &nonce, Forwarded: &forwarded})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, !*resp.Valid, "Redeem returned the wrong response")
|
||||
|
||||
// Already forwarded message, Redeem should return false
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&workingRemote{resp: true},
|
||||
&workingRemote{resp: true},
|
||||
}
|
||||
forwarded = true
|
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Hour))
|
||||
resp, err = ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: &nonce, Forwarded: &forwarded})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, !*resp.Valid, "Redeem returned the wrong response")
|
||||
|
||||
// Broken remotes, Redeem should return false
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&brokenRemote{},
|
||||
&brokenRemote{},
|
||||
}
|
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond))
|
||||
resp, err = ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: &nonce, Forwarded: &forwarded})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, !*resp.Valid, "Redeem returned the wrong response")
|
||||
}
|
|
@ -27,6 +27,7 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
|||
|
||||
type NonceMessage struct {
|
||||
Nonce *string `protobuf:"bytes,1,opt,name=nonce" json:"nonce,omitempty"`
|
||||
Forwarded *bool `protobuf:"varint,2,opt,name=forwarded" json:"forwarded,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
|
@ -64,6 +65,13 @@ func (m *NonceMessage) GetNonce() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *NonceMessage) GetForwarded() bool {
|
||||
if m != nil && m.Forwarded != nil {
|
||||
return *m.Forwarded
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ValidMessage struct {
|
||||
Valid *bool `protobuf:"varint,1,opt,name=valid" json:"valid,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
|
@ -111,18 +119,19 @@ func init() {
|
|||
func init() { proto.RegisterFile("nonce/proto/nonce.proto", fileDescriptor_9197b76ef104b424) }
|
||||
|
||||
var fileDescriptor_9197b76ef104b424 = []byte{
|
||||
// 164 bytes of a gzipped FileDescriptorProto
|
||||
// 189 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0xcb, 0xcf, 0x4b,
|
||||
0x4e, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x07, 0xb3, 0xf5, 0xc0, 0x6c, 0x21, 0x56, 0x30,
|
||||
0x47, 0x4a, 0x34, 0x39, 0xbf, 0x08, 0x26, 0x0d, 0x62, 0x42, 0x64, 0x95, 0x54, 0xb8, 0x78, 0xfc,
|
||||
0x47, 0x4a, 0x34, 0x39, 0xbf, 0x08, 0x26, 0x0d, 0x62, 0x42, 0x64, 0x95, 0x9c, 0xb8, 0x78, 0xfc,
|
||||
0x40, 0xf2, 0xbe, 0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0xa9, 0x42, 0x22, 0x5c, 0x10, 0xf5, 0x12, 0x8c,
|
||||
0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x10, 0x0e, 0x48, 0x55, 0x58, 0x62, 0x4e, 0x66, 0x0a, 0x92, 0xaa,
|
||||
0x32, 0x10, 0x1f, 0xac, 0x8a, 0x23, 0x08, 0xc2, 0x31, 0x2a, 0x84, 0x9a, 0x15, 0x9c, 0x5a, 0x54,
|
||||
0x96, 0x99, 0x9c, 0x2a, 0xa4, 0xcd, 0xc5, 0x0a, 0xe6, 0x0b, 0x71, 0xeb, 0x81, 0x6d, 0x74, 0xcd,
|
||||
0x2d, 0x28, 0xa9, 0x94, 0x12, 0xd6, 0x83, 0xb8, 0x0e, 0xd9, 0x5a, 0x25, 0x06, 0x21, 0x13, 0x2e,
|
||||
0xb6, 0xa0, 0xd4, 0x94, 0xd4, 0xd4, 0x5c, 0x21, 0x6c, 0x0a, 0xe0, 0xba, 0x90, 0x9d, 0xa1, 0xc4,
|
||||
0xe0, 0xc4, 0x1e, 0xc5, 0x0a, 0xf6, 0x07, 0x20, 0x00, 0x00, 0xff, 0xff, 0x38, 0x1f, 0x03, 0xab,
|
||||
0xff, 0x00, 0x00, 0x00,
|
||||
0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x10, 0x8e, 0x90, 0x0c, 0x17, 0x67, 0x5a, 0x7e, 0x51, 0x79, 0x62,
|
||||
0x51, 0x4a, 0x6a, 0x8a, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x47, 0x10, 0x42, 0x40, 0x49, 0x85, 0x8b,
|
||||
0x27, 0x2c, 0x31, 0x27, 0x33, 0x05, 0xc9, 0x8c, 0x32, 0x10, 0x1f, 0x6c, 0x06, 0x47, 0x10, 0x84,
|
||||
0x63, 0x54, 0x08, 0xb5, 0x29, 0x38, 0xb5, 0xa8, 0x2c, 0x33, 0x39, 0x55, 0x48, 0x9b, 0x8b, 0x15,
|
||||
0xcc, 0x17, 0xe2, 0xd6, 0x03, 0xbb, 0xc7, 0x35, 0xb7, 0xa0, 0xa4, 0x52, 0x4a, 0x58, 0x0f, 0xe2,
|
||||
0x76, 0x64, 0x47, 0x29, 0x31, 0x08, 0x99, 0x70, 0xb1, 0x05, 0xa5, 0xa6, 0xa4, 0xa6, 0xe6, 0x0a,
|
||||
0x61, 0x53, 0x00, 0xd7, 0x85, 0xec, 0x0c, 0x25, 0x06, 0x27, 0xf6, 0x28, 0x56, 0xb0, 0x2f, 0x01,
|
||||
0x01, 0x00, 0x00, 0xff, 0xff, 0x29, 0xf6, 0x52, 0xfd, 0x1d, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
|
|
@ -12,6 +12,7 @@ service NonceService {
|
|||
|
||||
message NonceMessage {
|
||||
optional string nonce = 1;
|
||||
optional bool forwarded = 2;
|
||||
}
|
||||
|
||||
message ValidMessage {
|
||||
|
|
|
@ -8,13 +8,24 @@
|
|||
"grpc": {
|
||||
"address": ":9101",
|
||||
"clientNames": [
|
||||
"wfe.boulder"
|
||||
"wfe.boulder",
|
||||
"nonce.boulder"
|
||||
]
|
||||
},
|
||||
"tls": {
|
||||
"caCertFile": "test/grpc-creds/minica.pem",
|
||||
"certFile": "test/grpc-creds/nonce.boulder/cert.pem",
|
||||
"keyFile": "test/grpc-creds/nonce.boulder/key.pem"
|
||||
}
|
||||
},
|
||||
"remoteNonceServices": [
|
||||
{
|
||||
"serverAddress": "nonce.boulder:9102",
|
||||
"timeout": "15s"
|
||||
},
|
||||
{
|
||||
"serverAddress": "nonce.boulder:9101",
|
||||
"timeout": "15s"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ command -v minica >/dev/null 2>&1 || {
|
|||
}
|
||||
|
||||
for HOSTNAME in admin-revoker.boulder expiration-mailer.boulder \
|
||||
ocsp-updater.boulder orphan-finder.boulder wfe.boulder akamai-purger.boulder ; do
|
||||
ocsp-updater.boulder orphan-finder.boulder wfe.boulder akamai-purger.boulder nonce.boulder ; do
|
||||
minica -domains ${HOSTNAME}
|
||||
done
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ def start(race_detection, fakeclock=None, config_dir=default_config_dir):
|
|||
[8002, './bin/boulder-ra --config %s --addr ra1.boulder:9094 --debug-addr :8002' % os.path.join(config_dir, "ra.json")],
|
||||
[8102, './bin/boulder-ra --config %s --addr ra2.boulder:9094 --debug-addr :8102' % os.path.join(config_dir, "ra.json")],
|
||||
[8111, './bin/nonce-service --config %s' % os.path.join(config_dir, "nonce.json")],
|
||||
[8112, './bin/nonce-service --config %s --addr nonce.boulder:9102 --debug-addr :8112' % os.path.join(config_dir, "nonce.json")],
|
||||
[4431, './bin/boulder-wfe2 --config %s' % os.path.join(config_dir, "wfe2.json")],
|
||||
[4000, './bin/boulder-wfe --config %s' % os.path.join(config_dir, "wfe.json")],
|
||||
])
|
||||
|
|
Loading…
Reference in New Issue