Skip to content

Commit

Permalink
Merge pull request #77 from guggero/macaroons
Browse files Browse the repository at this point in the history
Add macaroon authentication
  • Loading branch information
guggero authored Sep 11, 2020
2 parents c675def + 699d359 commit c38c9b9
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 78 deletions.
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

0 comments on commit c38c9b9

Please sign in to comment.