Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpc+lnd: add new invoice-only macaroon #904

Merged
merged 4 commits into from
Mar 21, 2018
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
55 changes: 36 additions & 19 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
defaultTLSKeyFilename = "tls.key"
defaultAdminMacFilename = "admin.macaroon"
defaultReadMacFilename = "readonly.macaroon"
defaultInvoiceMacFilename = "invoice.macaroon"
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "lnd.log"
Expand All @@ -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")
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down
46 changes: 37 additions & 9 deletions lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
38 changes: 34 additions & 4 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ var (
Entity: "info",
Action: "read",
},
{
Entity: "invoices",
Action: "read",
},
}

// writePermissions is a slice of all entities that allow write
Expand Down Expand Up @@ -98,6 +102,32 @@ var (
Entity: "info",
Action: "write",
},
{
Entity: "invoices",
Action: "write",
},
}

// invoicePermissions is a slice of all the entities that allows a user
// to only access calls that are related to invoices, so: streaming
// RPC's, generating, and listening invoices.
invoicePermissions = []bakery.Op{
{
Entity: "invoices",
Action: "read",
},
{
Entity: "invoices",
Action: "write",
},
{
Entity: "address",
Action: "read",
},
{
Entity: "address",
Action: "write",
},
}

// permissions maps RPC calls to the permissions they require.
Expand Down Expand Up @@ -188,19 +218,19 @@ var (
Action: "write",
}},
"/lnrpc.Lightning/AddInvoice": {{
Entity: "offchain",
Entity: "invoices",
Action: "write",
}},
"/lnrpc.Lightning/LookupInvoice": {{
Entity: "offchain",
Entity: "invoices",
Action: "read",
}},
"/lnrpc.Lightning/ListInvoices": {{
Entity: "offchain",
Entity: "invoices",
Action: "read",
}},
"/lnrpc.Lightning/SubscribeInvoices": {{
Entity: "offchain",
Entity: "invoices",
Action: "read",
}},
"/lnrpc.Lightning/SubscribeTransactions": {{
Expand Down