forked from NYDIG-OSS/lndsigner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lndsigner.go
278 lines (229 loc) · 7.62 KB
/
lndsigner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// Copyright (C) 2013-2017 The btcsuite developers
// Copyright (C) 2015-2016 The Decred developers
// Copyright (C) 2015-2022 Lightning Labs and The Lightning Network Developers
// Copyright (C) 2022 Bottlepay and The Lightning Network Developers
package lndsigner
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"github.com/hashicorp/vault/api"
"github.com/nydig-oss/lndsigner/vault"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
// ListenerWithSignal is a net.Listener that has an additional Ready channel
// that will be closed when a server starts listening.
type ListenerWithSignal struct {
net.Listener
// Ready will be closed by the server listening on Listener.
Ready chan struct{}
}
// ListenerCfg is a wrapper around custom listeners that can be passed to lnd
// when calling its main method.
type ListenerCfg struct {
// RPCListeners can be set to the listeners to use for the RPC server.
// If empty a regular network listener will be created.
RPCListeners []*ListenerWithSignal
}
// Main is the true entry point for lnd. It accepts a fully populated and
// validated main configuration struct and an optional listener config struct.
// This function starts all main system components then blocks until a signal
// is received on the shutdownChan at which point everything is shut down again.
func Main(cfg *Config, lisCfg ListenerCfg) error {
// mkErr makes it easy to return logged errors.
mkErr := func(format string, args ...interface{}) error {
signerLog.Errorf("Shutting down because error in main "+
"method: "+format, args...)
return fmt.Errorf(format, args...)
}
signerLog.Infow("Active Bitcoin network: ", "net",
cfg.ActiveNetParams.Name)
// Use defaults for vault client, including getting config from env.
vaultClient, err := api.NewClient(nil)
if err != nil {
return mkErr("error creating vault client: %v", err)
}
signerClient := vaultClient.Logical()
serverOpts, err := getTLSConfig(cfg)
if err != nil {
return mkErr("unable to load TLS credentials: %v", err)
}
// If we have chosen to start with a dedicated listener for the
// rpc server, we set it directly.
grpcListeners := append([]*ListenerWithSignal{}, lisCfg.RPCListeners...)
if len(grpcListeners) == 0 {
// Otherwise we create listeners from the RPCListeners defined
// in the config.
for _, grpcEndpoint := range cfg.RPCListeners {
// Start a gRPC server listening for HTTP/2
// connections.
lis, err := ListenOnAddress(grpcEndpoint)
if err != nil {
return mkErr("unable to listen on %s: %v",
grpcEndpoint, err)
}
defer lis.Close()
grpcListeners = append(
grpcListeners, &ListenerWithSignal{
Listener: lis,
Ready: make(chan struct{}),
},
)
}
}
// Initialize the rpcServer and add its interceptor to the server
// options.
rpcServer := newRPCServer(cfg, signerClient)
serverOpts = append(
serverOpts,
grpc.ChainUnaryInterceptor(rpcServer.intercept),
)
// Create the GRPC server with the TLS and interceptor configuration.
grpcServer := grpc.NewServer(serverOpts...)
defer grpcServer.Stop()
// Register our implementation of the gRPC interface exported by the
// rpcServer.
err = rpcServer.RegisterWithGrpcServer(grpcServer)
if err != nil {
return mkErr("error registering gRPC server: %v", err)
}
// Now that both the WalletUnlocker and LightningService have been
// registered with the GRPC server, we can start listening.
err = startGrpcListen(cfg, grpcServer, grpcListeners)
if err != nil {
return mkErr("error starting gRPC listener: %v", err)
}
// Wait for shutdown signal from the interrupt handler.
signerLog.Infof("Press ctrl-c to exit")
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-sigint
return nil
}
// getTLSConfig returns a TLS configuration for the gRPC server.
func getTLSConfig(cfg *Config) ([]grpc.ServerOption, error) {
// The certData returned here is just a wrapper around the PEM blocks
// loaded from the file. The PEM is not yet fully parsed but a basic
// check is performed that the certificate and private key actually
// belong together.
certData, err := tls.LoadX509KeyPair(
cfg.TLSCertPath, cfg.TLSKeyPath,
)
if err != nil {
return nil, err
}
// Now parse the the PEM block of the certificate.
_, err = x509.ParseCertificate(certData.Certificate[0])
if err != nil {
return nil, err
}
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{certData},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
},
}
serverCreds := credentials.NewTLS(tlsCfg)
serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)}
return serverOpts, nil
}
// fileExists reports whether the named file or directory exists.
// This function is taken from https://github.com/btcsuite/btcd
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// startGrpcListen starts the GRPC server on the passed listeners.
func startGrpcListen(cfg *Config, grpcServer *grpc.Server,
listeners []*ListenerWithSignal) error {
// Use a WaitGroup so we can be sure the instructions on how to input the
// password is the last thing to be printed to the console.
var wg sync.WaitGroup
for _, lis := range listeners {
wg.Add(1)
go func(lis *ListenerWithSignal) {
signerLog.Infof("RPC server listening on %s", lis.Addr())
// Close the ready chan to indicate we are listening.
close(lis.Ready)
wg.Done()
_ = grpcServer.Serve(lis)
}(lis)
}
// Wait for gRPC servers to be up running.
wg.Wait()
return nil
}
// parseNetwork parses the network type of the given address.
func parseNetwork(addr net.Addr) string {
switch addr := addr.(type) {
// TCP addresses resolved through net.ResolveTCPAddr give a default
// network of "tcp", so we'll map back the correct network for the given
// address. This ensures that we can listen on the correct interface
// (IPv4 vs IPv6).
case *net.TCPAddr:
if addr.IP.To4() != nil {
return "tcp4"
}
return "tcp6"
default:
return addr.Network()
}
}
// ListenOnAddress creates a listener that listens on the given address.
func ListenOnAddress(addr net.Addr) (net.Listener, error) {
return net.Listen(parseNetwork(addr), addr.String())
}
type jsonAcctEl struct {
Xpub string `json:"extended_public_key"`
Path string `json:"derivation_path"`
}
// GetAccounts is currently used in integration testing, but will soon also
// be used in policy enforcement. For current status, see the branch at
// https://github.com/aakselrod/lndsigner/tree/offchain-ratelimiting
func GetAccounts(acctList string) (map[[3]uint32]string, error) {
accounts := make(map[[3]uint32]string)
elements := make(map[string][]*jsonAcctEl)
err := json.Unmarshal([]byte(acctList), &elements)
if err != nil {
return nil, err
}
acctElements, ok := elements["accounts"]
if !ok {
return nil, fmt.Errorf("no accounts returned in JSON")
}
for _, acctEl := range acctElements {
pathEls := strings.Split(acctEl.Path, "/")
if len(pathEls) != 4 || pathEls[0] != "m" {
return nil, fmt.Errorf("invalid derivation path")
}
var derPath [3]uint32
for idx, el := range pathEls[1:] {
if !strings.HasSuffix(el, "'") {
return nil, vault.ErrElementNotHardened
}
intEl, err := strconv.ParseUint(el[:len(el)-1], 10, 32)
if err != nil {
return nil, err
}
derPath[idx] = uint32(intEl)
}
accounts[derPath] = acctEl.Xpub
}
return accounts, nil
}