diff --git a/config.go b/config.go index b52bdd11..52436b12 100644 --- a/config.go +++ b/config.go @@ -35,6 +35,7 @@ const ( defaultTLSKeyFilename = "tls.key" defaultAdminMacFilename = "admin.macaroon" defaultReadMacFilename = "readonly.macaroon" + defaultInvoiceMacFilename = "invoice.macaroon" defaultLogLevel = "info" defaultLogDirname = "logs" defaultLogFilename = "lnd.log" @@ -57,14 +58,17 @@ const ( ) var ( - defaultLndDir = btcutil.AppDataDir("lnd", false) - defaultConfigFile = filepath.Join(defaultLndDir, defaultConfigFilename) - defaultDataDir = filepath.Join(defaultLndDir, defaultDataDirname) - defaultTLSCertPath = filepath.Join(defaultLndDir, defaultTLSCertFilename) - defaultTLSKeyPath = filepath.Join(defaultLndDir, defaultTLSKeyFilename) - defaultAdminMacPath = filepath.Join(defaultLndDir, defaultAdminMacFilename) - defaultReadMacPath = filepath.Join(defaultLndDir, defaultReadMacFilename) - defaultLogDir = filepath.Join(defaultLndDir, defaultLogDirname) + defaultLndDir = btcutil.AppDataDir("lnd", false) + defaultConfigFile = filepath.Join(defaultLndDir, defaultConfigFilename) + defaultDataDir = filepath.Join(defaultLndDir, defaultDataDirname) + defaultLogDir = filepath.Join(defaultLndDir, defaultLogDirname) + + defaultTLSCertPath = filepath.Join(defaultLndDir, defaultTLSCertFilename) + defaultTLSKeyPath = filepath.Join(defaultLndDir, defaultTLSKeyFilename) + + defaultAdminMacPath = filepath.Join(defaultLndDir, defaultAdminMacFilename) + defaultReadMacPath = filepath.Join(defaultLndDir, defaultReadMacFilename) + defaultInvoiceMacPath = filepath.Join(defaultLndDir, defaultInvoiceMacFilename) defaultBtcdDir = btcutil.AppDataDir("btcd", false) defaultBtcdRPCCertFile = filepath.Join(defaultBtcdDir, "rpc.cert") @@ -150,6 +154,7 @@ type config struct { NoMacaroons bool `long:"no-macaroons" description:"Disable macaroon authentication"` AdminMacPath string `long:"adminmacaroonpath" description:"Path to write the admin macaroon for lnd's RPC and REST services if it doesn't exist"` ReadMacPath string `long:"readonlymacaroonpath" description:"Path to write the read-only macaroon for lnd's RPC and REST services if it doesn't exist"` + InvoiceMacPath string `long:"invoicemacaroonpath" description:"Path to the invoice-only macaroon for lnd's RPC and REST services if it doesn't exist"` LogDir string `long:"logdir" description:"Directory to log output."` RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections"` @@ -205,15 +210,16 @@ type config struct { // 4) Parse CLI options and overwrite/add any specified options func loadConfig() (*config, error) { defaultCfg := config{ - LndDir: defaultLndDir, - ConfigFile: defaultConfigFile, - DataDir: defaultDataDir, - DebugLevel: defaultLogLevel, - TLSCertPath: defaultTLSCertPath, - TLSKeyPath: defaultTLSKeyPath, - AdminMacPath: defaultAdminMacPath, - ReadMacPath: defaultReadMacPath, - LogDir: defaultLogDir, + LndDir: defaultLndDir, + ConfigFile: defaultConfigFile, + DataDir: defaultDataDir, + DebugLevel: defaultLogLevel, + TLSCertPath: defaultTLSCertPath, + TLSKeyPath: defaultTLSKeyPath, + AdminMacPath: defaultAdminMacPath, + InvoiceMacPath: defaultInvoiceMacPath, + ReadMacPath: defaultReadMacPath, + LogDir: defaultLogDir, Bitcoin: &chainConfig{ MinHTLC: defaultBitcoinMinHTLCMSat, BaseFee: defaultBitcoinBaseFeeMSat, @@ -282,6 +288,7 @@ func loadConfig() (*config, error) { defaultCfg.TLSCertPath = filepath.Join(lndDir, defaultTLSCertFilename) defaultCfg.TLSKeyPath = filepath.Join(lndDir, defaultTLSKeyFilename) defaultCfg.AdminMacPath = filepath.Join(lndDir, defaultAdminMacFilename) + defaultCfg.InvoiceMacPath = filepath.Join(lndDir, defaultInvoiceMacFilename) defaultCfg.ReadMacPath = filepath.Join(lndDir, defaultReadMacFilename) defaultCfg.LogDir = filepath.Join(lndDir, defaultLogDirname) } @@ -327,6 +334,7 @@ func loadConfig() (*config, error) { cfg.TLSKeyPath = cleanAndExpandPath(cfg.TLSKeyPath) cfg.AdminMacPath = cleanAndExpandPath(cfg.AdminMacPath) cfg.ReadMacPath = cleanAndExpandPath(cfg.ReadMacPath) + cfg.InvoiceMacPath = cleanAndExpandPath(cfg.InvoiceMacPath) cfg.LogDir = cleanAndExpandPath(cfg.LogDir) cfg.BtcdMode.Dir = cleanAndExpandPath(cfg.BtcdMode.Dir) cfg.LtcdMode.Dir = cleanAndExpandPath(cfg.LtcdMode.Dir) @@ -591,10 +599,19 @@ func loadConfig() (*config, error) { // directory has changed from the default path, then we'll also update // the path for the macaroons to be generated. if cfg.DataDir != defaultDataDir && cfg.AdminMacPath == defaultAdminMacPath { - cfg.AdminMacPath = filepath.Join(cfg.DataDir, defaultAdminMacFilename) + cfg.AdminMacPath = filepath.Join( + cfg.DataDir, defaultAdminMacFilename, + ) } if cfg.DataDir != defaultDataDir && cfg.ReadMacPath == defaultReadMacPath { - cfg.ReadMacPath = filepath.Join(cfg.DataDir, defaultReadMacFilename) + cfg.ReadMacPath = filepath.Join( + cfg.DataDir, defaultReadMacFilename, + ) + } + if cfg.DataDir != defaultDataDir && cfg.InvoiceMacPath == defaultInvoiceMacPath { + cfg.InvoiceMacPath = filepath.Join( + cfg.DataDir, defaultInvoiceMacPath, + ) } // Append the network type to the log directory so it is "namespaced" diff --git a/lnd.go b/lnd.go index c73225f3..9a0e1e71 100644 --- a/lnd.go +++ b/lnd.go @@ -228,10 +228,15 @@ func lndMain() error { srvrLog.Error(err) return err } + // Create macaroon files for lncli to use if they don't exist. - if !fileExists(cfg.AdminMacPath) && !fileExists(cfg.ReadMacPath) { - err = genMacaroons(ctx, macaroonService, - cfg.AdminMacPath, cfg.ReadMacPath) + if !fileExists(cfg.AdminMacPath) && !fileExists(cfg.ReadMacPath) && + !fileExists(cfg.InvoiceMacPath) { + + err = genMacaroons( + ctx, macaroonService, cfg.AdminMacPath, + cfg.ReadMacPath, cfg.InvoiceMacPath, + ) if err != nil { ltndLog.Errorf("unable to create macaroon "+ "files: %v", err) @@ -751,12 +756,33 @@ func genCertPair(certFile, keyFile string) error { // genMacaroons generates a pair of macaroon files; one admin-level and one // read-only. These can also be used to generate more granular macaroons. -func genMacaroons(ctx context.Context, svc *macaroons.Service, admFile, - roFile string) error { +func genMacaroons(ctx context.Context, svc *macaroons.Service, + admFile, roFile, invoiceFile string) error { + + // First, we'll generate a macaroon that only allows the caller to + // access invoice related calls. This is useful for merchants and other + // services to allow an isolated instance that can only query and + // modify invoices. + invoiceMac, err := svc.Oven.NewMacaroon( + ctx, bakery.LatestVersion, nil, invoicePermissions..., + ) + if err != nil { + return err + } + invoiceMacBytes, err := invoiceMac.M().MarshalBinary() + if err != nil { + return err + } + err = ioutil.WriteFile(invoiceFile, invoiceMacBytes, 0644) + if err != nil { + os.Remove(invoiceFile) + return err + } // Generate the read-only macaroon and write it to a file. - roMacaroon, err := svc.Oven.NewMacaroon(ctx, bakery.LatestVersion, nil, - readPermissions...) + roMacaroon, err := svc.Oven.NewMacaroon( + ctx, bakery.LatestVersion, nil, readPermissions..., + ) if err != nil { return err } @@ -770,8 +796,10 @@ func genMacaroons(ctx context.Context, svc *macaroons.Service, admFile, } // Generate the admin macaroon and write it to a file. - admMacaroon, err := svc.Oven.NewMacaroon(ctx, bakery.LatestVersion, - nil, append(readPermissions, writePermissions...)...) + adminPermissions := append(readPermissions, writePermissions...) + admMacaroon, err := svc.Oven.NewMacaroon( + ctx, bakery.LatestVersion, nil, adminPermissions..., + ) if err != nil { return err }