lnd: generate default macaroons independently

This modifies the `genMacaroons` logic to indepently check for each of
the three default macaroons (admin, readonly, invoice) and generate
whichever are missing. Previously, this was an all or nothing routine.
In other words, either all three didn't exist on disk and all three are
created, or no macaroons are created. Although that works for the first
run of a new node, it can result in inconsistent states if only one or
two of the macaroons is deleted.

See https://github.com/lightningnetwork/lnd/discussions/7566.
This commit is contained in:
Daniel McNally 2023-04-11 13:17:15 -04:00
parent fdc1ae9d57
commit 16463d4bd4
No known key found for this signature in database
3 changed files with 57 additions and 37 deletions

View file

@ -470,14 +470,10 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
// If the user requested a stateless initialization, no macaroon // If the user requested a stateless initialization, no macaroon
// files should be created. // files should be created.
if !walletInitParams.StatelessInit && if !walletInitParams.StatelessInit {
!lnrpc.FileExists(d.cfg.AdminMacPath) && // Create default macaroon files for lncli to use if
!lnrpc.FileExists(d.cfg.ReadMacPath) && // they don't exist.
!lnrpc.FileExists(d.cfg.InvoiceMacPath) { err = genDefaultMacaroons(
// Create macaroon files for lncli to use if they don't
// exist.
err = genMacaroons(
ctx, macaroonService, d.cfg.AdminMacPath, ctx, macaroonService, d.cfg.AdminMacPath,
d.cfg.ReadMacPath, d.cfg.InvoiceMacPath, d.cfg.ReadMacPath, d.cfg.InvoiceMacPath,
) )

View file

@ -13,8 +13,14 @@
the entire retribution struct. This reduces the amount of data that needs to the entire retribution struct. This reduces the amount of data that needs to
be held in memory. be held in memory.
## Misc
* [Generate default macaroons
independently](https://github.com/lightningnetwork/lnd/pull/7592) on wallet
unlock or create.
# Contributors (Alphabetical Order) # Contributors (Alphabetical Order)
* Daniel McNally
* Elle Mouton * Elle Mouton
* Jordi Montes * Jordi Montes

56
lnd.go
View file

@ -690,46 +690,64 @@ func bakeMacaroon(ctx context.Context, svc *macaroons.Service,
return mac.M().MarshalBinary() return mac.M().MarshalBinary()
} }
// genMacaroons generates three macaroon files; one admin-level, one for // saveMacaroon bakes a macaroon with the specified macaroon permissions and
// invoice access and one read-only. These can also be used to generate more // writes it to a file with the given filename and file permissions.
// granular macaroons. func saveMacaroon(ctx context.Context, svc *macaroons.Service, filename string,
func genMacaroons(ctx context.Context, svc *macaroons.Service, macaroonPermissions []bakery.Op, filePermissions os.FileMode) error {
macaroonBytes, err := bakeMacaroon(ctx, svc, macaroonPermissions)
if err != nil {
return err
}
err = os.WriteFile(filename, macaroonBytes, filePermissions)
if err != nil {
_ = os.Remove(filename)
return err
}
return nil
}
// genDefaultMacaroons checks for three default macaroon files and generates
// them if they do not exist; one admin-level, one for invoice access and one
// read-only. Each macaroon is checked and created independently to ensure all
// three exist. The admin macaroon can also be used to generate more granular
// macaroons.
func genDefaultMacaroons(ctx context.Context, svc *macaroons.Service,
admFile, roFile, invoiceFile string) error { admFile, roFile, invoiceFile string) error {
// First, we'll generate a macaroon that only allows the caller to // First, we'll generate a macaroon that only allows the caller to
// access invoice related calls. This is useful for merchants and other // access invoice related calls. This is useful for merchants and other
// services to allow an isolated instance that can only query and // services to allow an isolated instance that can only query and
// modify invoices. // modify invoices.
invoiceMacBytes, err := bakeMacaroon(ctx, svc, invoicePermissions) if !lnrpc.FileExists(invoiceFile) {
err := saveMacaroon(
ctx, svc, invoiceFile, invoicePermissions, 0644,
)
if err != nil { if err != nil {
return err 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. // Generate the read-only macaroon and write it to a file.
roBytes, err := bakeMacaroon(ctx, svc, readPermissions) if !lnrpc.FileExists(roFile) {
err := saveMacaroon(
ctx, svc, roFile, readPermissions, 0644,
)
if err != nil { if err != nil {
return err return err
} }
if err = ioutil.WriteFile(roFile, roBytes, 0644); err != nil {
_ = os.Remove(roFile)
return err
} }
// Generate the admin macaroon and write it to a file. // Generate the admin macaroon and write it to a file.
admBytes, err := bakeMacaroon(ctx, svc, adminPermissions()) if !lnrpc.FileExists(admFile) {
err := saveMacaroon(
ctx, svc, admFile, adminPermissions(),
adminMacaroonFilePermissions,
)
if err != nil { if err != nil {
return err return err
} }
err = ioutil.WriteFile(admFile, admBytes, adminMacaroonFilePermissions)
if err != nil {
_ = os.Remove(admFile)
return err
} }
return nil return nil