Skip to content

Commit bf00338

Browse files
diorwavekrasi
andauthored
reporter: add mTLS support for remote signer connection (tellor-io#35)
* Add remote signer keyring to reporter client via --remote-signer-addr flag * Add mTLS support for remote signer in reporter client * fix: vendor signer api; restore grpc/rpc managers + password-file logic dropped in main merge --------- Co-authored-by: krasi <root@ns1028910.ip-40-160-21.us>
1 parent ca118f8 commit bf00338

3 files changed

Lines changed: 35 additions & 20 deletions

File tree

cmd/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ func init() {
168168
rootCmd.Flags().Duration("refresh-gas-estimates-interval", 12*time.Hour, "Interval for resetting cached gas estimates and gas-adjustment levels (<=0 disables)")
169169
// Remote signer: when set, tx signing is delegated to the remote signer service instead of the local keyring
170170
rootCmd.Flags().String("remote-signer-addr", "", "gRPC address of the remote signer service (e.g. localhost:9191). When set, tx signing uses the remote signer instead of the local keyring.")
171+
rootCmd.Flags().String("remote-signer-ca-cert", "", "Path to the CA certificate for verifying the remote signer's TLS certificate.")
172+
rootCmd.Flags().String("remote-signer-client-cert", "", "Path to the client TLS certificate presented to the remote signer.")
173+
rootCmd.Flags().String("remote-signer-client-key", "", "Path to the client TLS private key.")
171174

172175
// Auto-bridge: keep wallet at a fixed balance by bridging the excess to Ethereum
173176
rootCmd.Flags().Uint64(daemonflags.FlagAutoBalanceToKeep, 0, "Keep this amount of loya in the wallet; bridge any excess to Ethereum at --auto-balance-execution-time (0 = disabled)")

reporter/client/client.go

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import (
1212
"sync/atomic"
1313
"time"
1414

15+
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
1516
"github.com/ethereum/go-ethereum/common"
1617
"github.com/spf13/viper"
17-
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
1818
globalfeetypes "github.com/strangelove-ventures/globalfee/x/globalfee/types"
1919
customquery "github.com/tellor-io/layer-daemons/custom_query"
2020
daemonflags "github.com/tellor-io/layer-daemons/flags"
@@ -183,20 +183,17 @@ type Client struct {
183183
gasEstimator *gasEstimateState
184184

185185
// Resources that need cleanup
186-
grpcMu sync.RWMutex
187-
grpcConn *grpc.ClientConn
188-
grpcClient daemontypes.GrpcClient
189-
190-
rpcClient *rpchttp.HTTP // direct reference for WebSocket subscriptions
191-
grpcManager *grpcEndpointManager
192-
rpcMu sync.RWMutex
193-
rpcManager *rpcEndpointManager
194-
186+
grpcMu sync.RWMutex
187+
grpcConn *grpc.ClientConn
188+
grpcClient daemontypes.GrpcClient
189+
rpcClient *rpchttp.HTTP // direct reference for WebSocket subscriptions
190+
grpcManager *grpcEndpointManager
191+
rpcMu sync.RWMutex
192+
rpcManager *rpcEndpointManager
195193
remoteSignerConn *grpc.ClientConn // non-nil when --remote-signer-addr is set
196-
197-
wg sync.WaitGroup
198-
broadcastWg sync.WaitGroup // Tracks goroutines in BroadcastTxMsgToChain
199-
stopOnce sync.Once
194+
wg sync.WaitGroup
195+
broadcastWg sync.WaitGroup // Tracks goroutines in BroadcastTxMsgToChain
196+
stopOnce sync.Once
200197
}
201198

202199
// GetUniqueUnorderedTimeout generates a unique timeout timestamp for unordered transactions.
@@ -394,15 +391,17 @@ func (c *Client) Start(
394391
}
395392
c.rpcManager = rpcManager
396393
c.setRPCClient(rpcClientVal)
397-
c.logger.Info("CometBFT RPC client established", "endpoint", rpcEndpoint)
394+
c.logger.Info("CometBFT RPC client established", "endpoint", rpcEndpoint)
398395
encodingConfig := CreateEncodingConfig()
399396
c.cosmosCtx = c.cosmosCtx.WithCodec(encodingConfig.Codec).WithInterfaceRegistry(encodingConfig.InterfaceRegistry).WithTxConfig(encodingConfig.TxConfig)
400-
401397
remoteSignerAddr := viper.GetString("remote-signer-addr")
402398
if remoteSignerAddr != "" {
403399
// Use remote signer for tx signing — no local private key needed.
404400
c.logger.Info("Using remote signer for tx signing", "addr", remoteSignerAddr)
405-
kr, signerAccAddr, signerConn, err := newKeyringFromRemoteSigner(ctx, keyName, remoteSignerAddr)
401+
caCert := viper.GetString("remote-signer-ca-cert")
402+
clientCert := viper.GetString("remote-signer-client-cert")
403+
clientKey := viper.GetString("remote-signer-client-key")
404+
kr, signerAccAddr, signerConn, err := newKeyringFromRemoteSigner(ctx, keyName, remoteSignerAddr, caCert, clientCert, clientKey)
406405
if err != nil {
407406
return fmt.Errorf("failed to initialize remote signer keyring: %w", err)
408407
}
@@ -727,7 +726,7 @@ func (c *Client) RestorePrimaryEndpointsPeriodically(ctx context.Context, wg *sy
727726
return
728727
case <-ticker.C:
729728
c.tryRestorePrimaryRPCEndpoint(ctx)
730-
c.tryRestorePrimaryGRPCEndpoint(ctx)
729+
c.tryRestorePrimaryGRPCEndpoint(ctx)
731730
}
732731
}
733732
}

reporter/client/remote_signer_keyring.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77

88
signerv1 "github.com/tellor-io/bridge-remote-signer/api/gen/signer/v1"
9+
bridgetls "github.com/tellor-io/bridge-remote-signer/api/tls"
910
"google.golang.org/grpc"
1011
"google.golang.org/grpc/credentials/insecure"
1112

@@ -184,8 +185,20 @@ var _ keyring.Keyring = (*remoteSignerKeyring)(nil)
184185
// newKeyringFromRemoteSigner dials the remote signer at addr, fetches the public key
185186
// and bech32 address, and returns a keyring backed by the remote signer along with
186187
// the account address. The returned grpc.ClientConn must be closed when done.
187-
func newKeyringFromRemoteSigner(ctx context.Context, keyName, addr string) (keyring.Keyring, sdk.AccAddress, *grpc.ClientConn, error) {
188-
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
188+
// When caCert, clientCert, and clientKey are all non-empty, mTLS is used;
189+
// otherwise the connection falls back to insecure (for local/test use only).
190+
func newKeyringFromRemoteSigner(ctx context.Context, keyName, addr, caCert, clientCert, clientKey string) (keyring.Keyring, sdk.AccAddress, *grpc.ClientConn, error) {
191+
var dialOpt grpc.DialOption
192+
if caCert != "" && clientCert != "" && clientKey != "" {
193+
creds, err := bridgetls.NewClientCredentials(caCert, clientCert, clientKey, "bridge-signer")
194+
if err != nil {
195+
return nil, nil, nil, fmt.Errorf("load mTLS credentials: %w", err)
196+
}
197+
dialOpt = grpc.WithTransportCredentials(creds)
198+
} else {
199+
dialOpt = grpc.WithTransportCredentials(insecure.NewCredentials())
200+
}
201+
conn, err := grpc.NewClient(addr, dialOpt)
189202
if err != nil {
190203
return nil, nil, nil, fmt.Errorf("dial remote signer at %s: %w", addr, err)
191204
}

0 commit comments

Comments
 (0)