Skip to content

Commit c38c9b9

Browse files
authored
Merge pull request #77 from guggero/macaroons
Add macaroon authentication
2 parents c675def + 699d359 commit c38c9b9

File tree

11 files changed

+382
-78
lines changed

11 files changed

+382
-78
lines changed

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,25 @@ Faraday connects to a single instance of lnd. It requires access to macaroons fo
3232
By default, faraday runs on mainnet. The `--network` flag can be used to run in
3333
test environments.
3434

35-
## Transport security
35+
## Authentication and transport security
3636

37-
The gRPC and REST connections of `faraday` are encrypted with TLS the same way
38-
`lnd` is.
37+
The gRPC and REST connections of `faraday` are encrypted with TLS and secured
38+
with macaroon authentication the same way `lnd` is.
3939

40-
If no custom loop directory is set then the TLS certificate is stored in
41-
`~/.faraday/<network>/tls.cert`.
40+
If no custom faraday directory is set then the TLS certificate is stored in
41+
`~/.faraday/<network>/tls.cert` and the base macaroon in
42+
`~/.faraday/<network>/faraday.macaroon`.
4243

43-
The `frcli` command will pick up the file automatically on mainnet if no custom
44-
loop directory is used. For other networks it should be sufficient to add the
45-
`--network` flag to tell the CLI in what sub directory to look for the files.
44+
The `frcli` command will pick up these file automatically on mainnet if no
45+
custom faraday directory is used. For other networks it should be sufficient to
46+
add the `--network` flag to tell the CLI in what sub directory to look for the
47+
files.
48+
49+
For more information on macaroons,
50+
[see the macaroon documentation of lnd.](https://github.com/lightningnetwork/lnd/blob/master/docs/macaroons.md)
51+
52+
**NOTE**: Faraday's macaroons are independent from `lnd`'s. The same macaroon
53+
cannot be used for both `faraday` and `lnd`.
4654

4755
### Chain Backend
4856
Faraday offers node accounting services which require access to a Bitcoin node with `--txindex` set so that it can perform transaction lookup. Currently the `CloseReport` endpoint requires this connection, and will fail if it is not present. It is *strongly recommended* to provide this connection when utilizing the `NodeAudit` endpoint, but it is not required. This connection is *optional*, and all other endpoints will function if it is not configured.

cmd/frcli/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ var (
2828
Value: faraday.DefaultTLSCertPath,
2929
}
3030
macaroonPathFlag = cli.StringFlag{
31-
Name: "macaroonpath",
32-
Usage: "path to macaroon file, only needed if faraday runs " +
33-
"in the same process as lnd (GrUB)",
31+
Name: "macaroonpath",
32+
Usage: "path to macaroon file",
33+
Value: faraday.DefaultMacaroonPath,
3434
}
3535
)
3636

cmd/frcli/utils.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,20 @@ func getClientConn(ctx *cli.Context) *grpc.ClientConn {
9696
fatal(err)
9797
}
9898

99+
// We always need to send a macaroon.
100+
macOption, err := readMacaroon(macaroonPath)
101+
if err != nil {
102+
fatal(err)
103+
}
104+
99105
// We need to use a custom dialer so we can also connect to unix sockets
100106
// and not just TCP addresses.
101107
genericDialer := clientAddressDialer(defaultRPCPort)
102108

103109
opts := []grpc.DialOption{
104110
grpc.WithContextDialer(genericDialer),
105111
grpc.WithDefaultCallOptions(maxMsgRecvSize),
112+
macOption,
106113
}
107114

108115
// TLS cannot be disabled, we'll always have a cert file to read.
@@ -112,11 +119,6 @@ func getClientConn(ctx *cli.Context) *grpc.ClientConn {
112119
tlsCertPath, err))
113120
}
114121

115-
// Macaroons are not yet enabled by default.
116-
if macaroonPath != "" {
117-
opts = append(opts, readMacaroon(macaroonPath))
118-
}
119-
120122
opts = append(opts, grpc.WithTransportCredentials(creds))
121123

122124
conn, err := grpc.Dial(ctx.GlobalString("rpcserver"), opts...)
@@ -140,27 +142,34 @@ func extractPathArgs(ctx *cli.Context) (string, string, error) {
140142
}
141143

142144
// We'll now fetch the faradaydir so we can make a decision on how to
143-
// properly read the cert. This will either be the default, or will have
144-
// been overwritten by the end user.
145+
// properly read the macaroon and cert files. This will either be the
146+
// default, or will have been overwritten by the end user.
145147
faradayDir := lncfg.CleanAndExpandPath(ctx.GlobalString(
146148
faradayDirFlag.Name,
147149
))
148150
tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
149151
tlsCertFlag.Name,
150152
))
153+
macPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
154+
macaroonPathFlag.Name,
155+
))
151156

152157
// If a custom faraday directory was set, we'll also check if a custom
153-
// path for the TLS cert file was set as well. If not, we'll override
154-
// the path so they can be found within the custom faraday directory.
158+
// path for the TLS cert and macaroon file was set as well. If not,
159+
// we'll override the path so they can be found within the custom
160+
// faraday directory.
155161
if faradayDir != faraday.FaradayDirBase ||
156162
networkStr != faraday.DefaultNetwork {
157163

158164
tlsCertPath = filepath.Join(
159165
faradayDir, networkStr, faraday.DefaultTLSCertFilename,
160166
)
167+
macPath = filepath.Join(
168+
faradayDir, networkStr, faraday.DefaultMacaroonFilename,
169+
)
161170
}
162171

163-
return tlsCertPath, ctx.GlobalString(macaroonPathFlag.Name), nil
172+
return tlsCertPath, macPath, nil
164173
}
165174

166175
// ClientAddressDialer parsed client address and returns a dialer.
@@ -187,16 +196,16 @@ func clientAddressDialer(defaultPort string) func(context.Context,
187196
//
188197
// TODO(guggero): Provide this function in lnd's macaroon package and use it
189198
// from there.
190-
func readMacaroon(macPath string) grpc.DialOption {
199+
func readMacaroon(macPath string) (grpc.DialOption, error) {
191200
// Load the specified macaroon file.
192201
macBytes, err := ioutil.ReadFile(macPath)
193202
if err != nil {
194-
fatal(fmt.Errorf("unable to read macaroon path : %v", err))
203+
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
195204
}
196205

197206
mac := &macaroon.Macaroon{}
198207
if err = mac.UnmarshalBinary(macBytes); err != nil {
199-
fatal(fmt.Errorf("unable to decode macaroon: %v", err))
208+
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
200209
}
201210

202211
macConstraints := []macaroons.Constraint{
@@ -216,12 +225,12 @@ func readMacaroon(macPath string) grpc.DialOption {
216225
// Apply constraints to the macaroon.
217226
constrainedMac, err := macaroons.AddConstraints(mac, macConstraints...)
218227
if err != nil {
219-
fatal(err)
228+
return nil, err
220229
}
221230

222231
// Now we append the macaroon credentials to the dial options.
223232
cred := macaroons.NewMacaroonCredential(constrainedMac)
224-
return grpc.WithPerRPCCredentials(cred)
233+
return grpc.WithPerRPCCredentials(cred), nil
225234
}
226235

227236
// parseChannelPoint parses a funding txid and output index from the command

config.go

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ import (
66
"fmt"
77
"os"
88
"path/filepath"
9-
"strings"
109
"time"
1110

1211
"github.com/btcsuite/btcutil"
13-
"github.com/jessevdk/go-flags"
1412
"github.com/lightninglabs/lndclient"
1513
"github.com/lightningnetwork/lnd/build"
1614
"github.com/lightningnetwork/lnd/cert"
@@ -60,6 +58,16 @@ var (
6058
DefaultTLSKeyPath = filepath.Join(
6159
FaradayDirBase, DefaultNetwork, DefaultTLSKeyFilename,
6260
)
61+
62+
// DefaultMacaroonFilename is the default file name for the
63+
// autogenerated faraday macaroon.
64+
DefaultMacaroonFilename = "faraday.macaroon"
65+
66+
// DefaultMacaroonPath is the default full path of the base faraday
67+
// macaroon.
68+
DefaultMacaroonPath = filepath.Join(
69+
FaradayDirBase, DefaultNetwork, DefaultMacaroonFilename,
70+
)
6371
)
6472

6573
type LndConfig struct {
@@ -78,7 +86,7 @@ type Config struct { //nolint:maligned
7886
Lnd *LndConfig `group:"lnd" namespace:"lnd"`
7987

8088
// FaradayDir is the main directory where faraday stores all its data.
81-
FaradayDir string `long:"faradaydir" description:"The directory for all of faraday's data. If set, this option overwrites --tlscertpath and --tlskeypath."`
89+
FaradayDir string `long:"faradaydir" description:"The directory for all of faraday's data. If set, this option overwrites --macaroonpath, --tlscertpath and --tlskeypath."`
8290

8391
// ChainConn specifies whether to attempt connecting to a bitcoin backend.
8492
ChainConn bool `long:"connect_bitcoin" description:"Whether to attempt to connect to a backing bitcoin node. Some endpoints will not be available if this option is not enabled."`
@@ -102,6 +110,8 @@ type Config struct { //nolint:maligned
102110
TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed."`
103111
TLSDisableAutofill bool `long:"tlsdisableautofill" description:"Do not include the interface IPs or the system hostname in TLS certificate, use first --tlsextradomain as Common Name instead, if set."`
104112

113+
MacaroonPath string `long:"macaroonpath" description:"Path to write the macaroon for faraday's RPC and REST services if it doesn't exist."`
114+
105115
// RPCListen is the listen address for the faraday rpc server.
106116
RPCListen string `long:"rpclisten" description:"Address to listen on for gRPC clients."`
107117

@@ -127,70 +137,61 @@ func DefaultConfig() Config {
127137
DebugLevel: defaultDebugLevel,
128138
TLSCertPath: DefaultTLSCertPath,
129139
TLSKeyPath: DefaultTLSKeyPath,
140+
MacaroonPath: DefaultMacaroonPath,
130141
RPCListen: defaultRPCListen,
131142
ChainConn: defaultChainConn,
132143
Bitcoin: chain.DefaultConfig,
133144
}
134145
}
135146

136-
// LoadConfig starts with a skeleton default config, and reads in user provided
137-
// configuration from the command line. It does not provide a full set of
138-
// defaults or validate user input because validation and sensible default
139-
// setting are performed by the lndclient package.
140-
func LoadConfig() (*Config, error) {
141-
// Start with a default config.
142-
config := DefaultConfig()
143-
144-
// Parse command line options to obtain user specified values.
145-
if _, err := flags.Parse(&config); err != nil {
146-
return nil, err
147-
}
148-
149-
// Show the version and exit if the version flag was specified.
150-
appName := filepath.Base(os.Args[0])
151-
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
152-
if config.ShowVersion {
153-
fmt.Println(appName, "version", Version())
154-
os.Exit(0)
155-
}
156-
147+
// ValidateConfig sanitizes all file system paths and makes sure no incompatible
148+
// configuration combinations are used.
149+
func ValidateConfig(config *Config) error {
157150
// Validate the network.
158151
_, err := lndclient.Network(config.Network).ChainParams()
159152
if err != nil {
160-
return nil, fmt.Errorf("error validating network: %v", err)
153+
return fmt.Errorf("error validating network: %v", err)
161154
}
162155

163156
// Clean up and validate paths, then make sure the directories exist.
164157
config.FaradayDir = lncfg.CleanAndExpandPath(config.FaradayDir)
165158
config.TLSCertPath = lncfg.CleanAndExpandPath(config.TLSCertPath)
166159
config.TLSKeyPath = lncfg.CleanAndExpandPath(config.TLSKeyPath)
160+
config.MacaroonPath = lncfg.CleanAndExpandPath(config.MacaroonPath)
167161

168162
// Append the network type to faraday directory so they are "namespaced"
169163
// per network.
170164
config.FaradayDir = filepath.Join(config.FaradayDir, config.Network)
171165

172166
// Create the full path of directories now, including the network path.
173167
if err := os.MkdirAll(config.FaradayDir, os.ModePerm); err != nil {
174-
return nil, err
168+
return err
175169
}
176170

177-
// Since our faraday directory overrides our TLS dir values, make sure
178-
// that they are not set when faraday dir is set. We fail hard here
179-
// rather than overwriting and potentially confusing the user.
171+
// Since our faraday directory overrides our TLS dir and macaroon path
172+
// values, make sure that they are not set when faraday dir is set. We
173+
// fail hard here rather than overwriting and potentially confusing the
174+
// user.
180175
faradayDirSet := config.FaradayDir != FaradayDirBase
181176
if faradayDirSet {
182177
tlsCertPathSet := config.TLSCertPath != DefaultTLSCertPath
183178
tlsKeyPathSet := config.TLSKeyPath != DefaultTLSKeyPath
179+
macaroonPathSet := config.MacaroonPath != DefaultMacaroonPath
184180

185181
if tlsCertPathSet {
186-
return nil, fmt.Errorf("faradaydir overwrites " +
182+
return fmt.Errorf("faradaydir overwrites " +
187183
"tlscertpath, please only set one value")
188184
}
189185

190186
if tlsKeyPathSet {
191-
return nil, fmt.Errorf("faradaydir overwrites " +
187+
return fmt.Errorf("faradaydir overwrites " +
192188
"tlskeypath, please only set one value")
193189
}
190+
191+
if macaroonPathSet {
192+
return fmt.Errorf("faradaydir overwrites " +
193+
"macaroonpath, please only set one value")
194+
}
194195
}
195196

196197
// We want the TLS files to also be in the "namespaced" sub directory.
@@ -206,27 +207,32 @@ func LoadConfig() (*Config, error) {
206207
config.FaradayDir, DefaultTLSKeyFilename,
207208
)
208209
}
210+
if config.MacaroonPath == DefaultMacaroonPath {
211+
config.MacaroonPath = filepath.Join(
212+
config.FaradayDir, DefaultMacaroonFilename,
213+
)
214+
}
209215

210216
// If the user has opted into connecting to a bitcoin backend, check
211217
// that we have a rpc user and password, and that tls path is set if
212218
// required.
213219
if config.ChainConn {
214220
if config.Bitcoin.User == "" || config.Bitcoin.Password == "" {
215-
return nil, fmt.Errorf("rpc user and password " +
221+
return fmt.Errorf("rpc user and password " +
216222
"required when chainconn is set")
217223
}
218224

219225
if config.Bitcoin.UseTLS && config.Bitcoin.TLSPath == "" {
220-
return nil, fmt.Errorf("bitcoin.tlspath required " +
226+
return fmt.Errorf("bitcoin.tlspath required " +
221227
"when chainconn is set")
222228
}
223229
}
224230

225231
if err := build.ParseAndSetDebugLevels(config.DebugLevel, logWriter); err != nil {
226-
return nil, err
232+
return err
227233
}
228234

229-
return &config, nil
235+
return nil
230236
}
231237

232238
// getTLSConfig generates a new self signed certificate or refreshes an existing

faraday.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package faraday
33

44
import (
55
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
69

10+
"github.com/jessevdk/go-flags"
711
"github.com/lightninglabs/lndclient"
812
"github.com/lightningnetwork/lnd/signal"
913

@@ -14,12 +18,27 @@ import (
1418
// Main is the real entry point for faraday. It is required to ensure that
1519
// defers are properly executed when os.Exit() is called.
1620
func Main() error {
17-
config, err := LoadConfig()
18-
if err != nil {
19-
return fmt.Errorf("error loading config: %v", err)
21+
// Start with a default config.
22+
config := DefaultConfig()
23+
24+
// Parse command line options to obtain user specified values.
25+
if _, err := flags.Parse(&config); err != nil {
26+
return err
27+
}
28+
29+
// Show the version and exit if the version flag was specified.
30+
appName := filepath.Base(os.Args[0])
31+
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
32+
if config.ShowVersion {
33+
fmt.Println(appName, "version", Version())
34+
os.Exit(0)
2035
}
2136

22-
serverTLSCfg, restClientCreds, err := getTLSConfig(config)
37+
if err := ValidateConfig(&config); err != nil {
38+
return fmt.Errorf("error validating config: %v", err)
39+
}
40+
41+
serverTLSCfg, restClientCreds, err := getTLSConfig(&config)
2342
if err != nil {
2443
return fmt.Errorf("error loading TLS config: %v", err)
2544
}
@@ -49,6 +68,8 @@ func Main() error {
4968
CORSOrigin: config.CORSOrigin,
5069
TLSServerConfig: serverTLSCfg,
5170
RestClientConfig: restClientCreds,
71+
FaradayDir: config.FaradayDir,
72+
MacaroonPath: config.MacaroonPath,
5273
}
5374

5475
// If the client chose to connect to a bitcoin client, get one now.
@@ -62,7 +83,9 @@ func Main() error {
6283
server := frdrpc.NewRPCServer(cfg)
6384

6485
// Catch intercept signals, then start the server.
65-
signal.Intercept()
86+
if err := signal.Intercept(); err != nil {
87+
return err
88+
}
6689
if err := server.Start(); err != nil {
6790
return err
6891
}

0 commit comments

Comments
 (0)