Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,25 @@ Faraday connects to a single instance of lnd. It requires access to macaroons fo
By default, faraday runs on mainnet. The `--network` flag can be used to run in
test environments.

## Transport security
## Authentication and transport security

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

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

The `frcli` command will pick up the file automatically on mainnet if no custom
loop directory is used. For other networks it should be sufficient to add the
`--network` flag to tell the CLI in what sub directory to look for the files.
The `frcli` command will pick up these file automatically on mainnet if no
custom faraday directory is used. For other networks it should be sufficient to
add the `--network` flag to tell the CLI in what sub directory to look for the
files.

For more information on macaroons,
[see the macaroon documentation of lnd.](https://github.com/lightningnetwork/lnd/blob/master/docs/macaroons.md)

**NOTE**: Faraday's macaroons are independent from `lnd`'s. The same macaroon
cannot be used for both `faraday` and `lnd`.

### Chain Backend
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.
Expand Down
6 changes: 3 additions & 3 deletions cmd/frcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ var (
Value: faraday.DefaultTLSCertPath,
}
macaroonPathFlag = cli.StringFlag{
Name: "macaroonpath",
Usage: "path to macaroon file, only needed if faraday runs " +
"in the same process as lnd (GrUB)",
Name: "macaroonpath",
Usage: "path to macaroon file",
Value: faraday.DefaultMacaroonPath,
}
)

Expand Down
39 changes: 24 additions & 15 deletions cmd/frcli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,20 @@ func getClientConn(ctx *cli.Context) *grpc.ClientConn {
fatal(err)
}

// We always need to send a macaroon.
macOption, err := readMacaroon(macaroonPath)
if err != nil {
fatal(err)
}

// We need to use a custom dialer so we can also connect to unix sockets
// and not just TCP addresses.
genericDialer := clientAddressDialer(defaultRPCPort)

opts := []grpc.DialOption{
grpc.WithContextDialer(genericDialer),
grpc.WithDefaultCallOptions(maxMsgRecvSize),
macOption,
}

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

// Macaroons are not yet enabled by default.
if macaroonPath != "" {
opts = append(opts, readMacaroon(macaroonPath))
}

opts = append(opts, grpc.WithTransportCredentials(creds))

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

// We'll now fetch the faradaydir so we can make a decision on how to
// properly read the cert. This will either be the default, or will have
// been overwritten by the end user.
// properly read the macaroon and cert files. This will either be the
// default, or will have been overwritten by the end user.
faradayDir := lncfg.CleanAndExpandPath(ctx.GlobalString(
faradayDirFlag.Name,
))
tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
tlsCertFlag.Name,
))
macPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
macaroonPathFlag.Name,
))

// If a custom faraday directory was set, we'll also check if a custom
// path for the TLS cert file was set as well. If not, we'll override
// the path so they can be found within the custom faraday directory.
// path for the TLS cert and macaroon file was set as well. If not,
// we'll override the path so they can be found within the custom
// faraday directory.
if faradayDir != faraday.FaradayDirBase ||
networkStr != faraday.DefaultNetwork {

tlsCertPath = filepath.Join(
faradayDir, networkStr, faraday.DefaultTLSCertFilename,
)
macPath = filepath.Join(
faradayDir, networkStr, faraday.DefaultMacaroonFilename,
)
}

return tlsCertPath, ctx.GlobalString(macaroonPathFlag.Name), nil
return tlsCertPath, macPath, nil
}

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

mac := &macaroon.Macaroon{}
if err = mac.UnmarshalBinary(macBytes); err != nil {
fatal(fmt.Errorf("unable to decode macaroon: %v", err))
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
}

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

// Now we append the macaroon credentials to the dial options.
cred := macaroons.NewMacaroonCredential(constrainedMac)
return grpc.WithPerRPCCredentials(cred)
return grpc.WithPerRPCCredentials(cred), nil
}

// parseChannelPoint parses a funding txid and output index from the command
Expand Down
76 changes: 41 additions & 35 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"time"

"github.com/btcsuite/btcutil"
"github.com/jessevdk/go-flags"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/cert"
Expand Down Expand Up @@ -60,6 +58,16 @@ var (
DefaultTLSKeyPath = filepath.Join(
FaradayDirBase, DefaultNetwork, DefaultTLSKeyFilename,
)

// DefaultMacaroonFilename is the default file name for the
// autogenerated faraday macaroon.
DefaultMacaroonFilename = "faraday.macaroon"

// DefaultMacaroonPath is the default full path of the base faraday
// macaroon.
DefaultMacaroonPath = filepath.Join(
FaradayDirBase, DefaultNetwork, DefaultMacaroonFilename,
)
)

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

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

// ChainConn specifies whether to attempt connecting to a bitcoin backend.
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."`
Expand All @@ -102,6 +110,8 @@ type Config struct { //nolint:maligned
TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed."`
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."`

MacaroonPath string `long:"macaroonpath" description:"Path to write the macaroon for faraday's RPC and REST services if it doesn't exist."`

// RPCListen is the listen address for the faraday rpc server.
RPCListen string `long:"rpclisten" description:"Address to listen on for gRPC clients."`

Expand All @@ -127,70 +137,61 @@ func DefaultConfig() Config {
DebugLevel: defaultDebugLevel,
TLSCertPath: DefaultTLSCertPath,
TLSKeyPath: DefaultTLSKeyPath,
MacaroonPath: DefaultMacaroonPath,
RPCListen: defaultRPCListen,
ChainConn: defaultChainConn,
Bitcoin: chain.DefaultConfig,
}
}

// LoadConfig starts with a skeleton default config, and reads in user provided
// configuration from the command line. It does not provide a full set of
// defaults or validate user input because validation and sensible default
// setting are performed by the lndclient package.
func LoadConfig() (*Config, error) {
// Start with a default config.
config := DefaultConfig()

// Parse command line options to obtain user specified values.
if _, err := flags.Parse(&config); err != nil {
return nil, err
}

// Show the version and exit if the version flag was specified.
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
if config.ShowVersion {
fmt.Println(appName, "version", Version())
os.Exit(0)
}

// ValidateConfig sanitizes all file system paths and makes sure no incompatible
// configuration combinations are used.
func ValidateConfig(config *Config) error {
// Validate the network.
_, err := lndclient.Network(config.Network).ChainParams()
if err != nil {
return nil, fmt.Errorf("error validating network: %v", err)
return fmt.Errorf("error validating network: %v", err)
}

// Clean up and validate paths, then make sure the directories exist.
config.FaradayDir = lncfg.CleanAndExpandPath(config.FaradayDir)
config.TLSCertPath = lncfg.CleanAndExpandPath(config.TLSCertPath)
config.TLSKeyPath = lncfg.CleanAndExpandPath(config.TLSKeyPath)
config.MacaroonPath = lncfg.CleanAndExpandPath(config.MacaroonPath)

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

// Create the full path of directories now, including the network path.
if err := os.MkdirAll(config.FaradayDir, os.ModePerm); err != nil {
return nil, err
return err
}

// Since our faraday directory overrides our TLS dir values, make sure
// that they are not set when faraday dir is set. We fail hard here
// rather than overwriting and potentially confusing the user.
// Since our faraday directory overrides our TLS dir and macaroon path
// values, make sure that they are not set when faraday dir is set. We
// fail hard here rather than overwriting and potentially confusing the
// user.
faradayDirSet := config.FaradayDir != FaradayDirBase
if faradayDirSet {
tlsCertPathSet := config.TLSCertPath != DefaultTLSCertPath
tlsKeyPathSet := config.TLSKeyPath != DefaultTLSKeyPath
macaroonPathSet := config.MacaroonPath != DefaultMacaroonPath

if tlsCertPathSet {
return nil, fmt.Errorf("faradaydir overwrites " +
return fmt.Errorf("faradaydir overwrites " +
"tlscertpath, please only set one value")
}

if tlsKeyPathSet {
return nil, fmt.Errorf("faradaydir overwrites " +
return fmt.Errorf("faradaydir overwrites " +
"tlskeypath, please only set one value")
}

if macaroonPathSet {
return fmt.Errorf("faradaydir overwrites " +
"macaroonpath, please only set one value")
}
}

// We want the TLS files to also be in the "namespaced" sub directory.
Expand All @@ -206,27 +207,32 @@ func LoadConfig() (*Config, error) {
config.FaradayDir, DefaultTLSKeyFilename,
)
}
if config.MacaroonPath == DefaultMacaroonPath {
config.MacaroonPath = filepath.Join(
config.FaradayDir, DefaultMacaroonFilename,
)
}

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

if config.Bitcoin.UseTLS && config.Bitcoin.TLSPath == "" {
return nil, fmt.Errorf("bitcoin.tlspath required " +
return fmt.Errorf("bitcoin.tlspath required " +
"when chainconn is set")
}
}

if err := build.ParseAndSetDebugLevels(config.DebugLevel, logWriter); err != nil {
return nil, err
return err
}

return &config, nil
return nil
}

// getTLSConfig generates a new self signed certificate or refreshes an existing
Expand Down
33 changes: 28 additions & 5 deletions faraday.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package faraday

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/jessevdk/go-flags"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/signal"

Expand All @@ -14,12 +18,27 @@ import (
// Main is the real entry point for faraday. It is required to ensure that
// defers are properly executed when os.Exit() is called.
func Main() error {
config, err := LoadConfig()
if err != nil {
return fmt.Errorf("error loading config: %v", err)
// Start with a default config.
config := DefaultConfig()

// Parse command line options to obtain user specified values.
if _, err := flags.Parse(&config); err != nil {
return err
}

// Show the version and exit if the version flag was specified.
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
if config.ShowVersion {
fmt.Println(appName, "version", Version())
os.Exit(0)
}

serverTLSCfg, restClientCreds, err := getTLSConfig(config)
if err := ValidateConfig(&config); err != nil {
return fmt.Errorf("error validating config: %v", err)
}

serverTLSCfg, restClientCreds, err := getTLSConfig(&config)
if err != nil {
return fmt.Errorf("error loading TLS config: %v", err)
}
Expand Down Expand Up @@ -49,6 +68,8 @@ func Main() error {
CORSOrigin: config.CORSOrigin,
TLSServerConfig: serverTLSCfg,
RestClientConfig: restClientCreds,
FaradayDir: config.FaradayDir,
MacaroonPath: config.MacaroonPath,
}

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

// Catch intercept signals, then start the server.
signal.Intercept()
if err := signal.Intercept(); err != nil {
return err
}
if err := server.Start(); err != nil {
return err
}
Expand Down
Loading