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

Add "stateless remote mode" #160

Closed
3 of 5 tasks
guggero opened this issue Dec 14, 2020 · 5 comments · Fixed by #278
Closed
3 of 5 tasks

Add "stateless remote mode" #160

guggero opened this issue Dec 14, 2020 · 5 comments · Fixed by #278
Labels
enhancement New feature or request gRPC proxy ui

Comments

@guggero
Copy link
Member

guggero commented Dec 14, 2020

Goal

It should be possible to run LiT in a way that it requires no sensitive information to be available to it on disk and also does not store sensitive information to disk itself.
To enter that "stateless remote mode", I'd like to start LiT with just one flag: --lnd-mode=stateless-remote

This would just start the UI and then wait for user interaction there. In this mode, the UI would not ask for a password first but for a lndconnect format connection string. The connection string contains all information that is needed to connect to the remote lnd mode (IP address, TLS cert if necessary and macaroon). Once the string is provided, the LiT binary connects to the given lnd node and starts the integrated daemons (Loop, Pool, Faraday).

Challenges

  1. Loop, Pool, Faraday (="the daemons") still require each subserver macaroon to be available as a file to connect to lnd. This was fixed in lnd 0.11.1: allow custom macaroon to be used instead of requiring all subserver macaroons lndclient#19 but that fix hasn't been pulled into the daemons yet. We can even expand on that and make it possible to pass in a macaroon directly as a hex string instead of needing to pass a file name.
    • Needs a small change to lndclient that then needs to be pulled into Pool, Loop, Faraday and LiT.
  2. The daemons don't have a stateless init mode yet, meaning they'll leave their own RPC macaroons laying around on the disk (unencrypted).
    • When running in stateless-remote mode, the daemons wouldn't use their own macaroons (wouldn't even create a macaroon DB) but instead use the remote lnd node to validate their macaroons.
    • Because the macaroons for the daemons will contain permissions and/or RPC URIs that lnd doesn't know, this requires a new RPC in lnd that allows us to validate a macaroon without checking the permissions part of it.
    • When baking a macaroon, a new flag would be necessary to instruct lnd to not complain about permission pairs or URIs it doesn't know itself.
    • Users of the stateless remote mode would need to bake a "super admin macaroon" that gives access to lnd and all daemons. Basically containing all permissions that exist of all the 4 daemons.
  3. The daemons don't have an unlock mechanism yet, meaning their macaroon DB is unencrypted (or to be precise: the encryption uses a default password).
    • Would also be solved by turning off macaroons for the daemons in the stateless remote mode.
  4. LiT gives the user access to all RPCs with just a password. That Basic Auth mechanism is turned into a macaroon internally. But it means the LiT backend needs access to all daemon macaroons (and lnd's) internally.
    • The UI password would be replaced completely with the connection string. That could be stored in the browser's session storage by the UI scripts. The macaroon part could even be encrypted, causing the UI to ask for a password before using it. That way the encrypted string could also be stored in the longer term storage of the browser, effectively requiring the user to "log in" again after the browser is closed.

Steps to completion

  • (1) Add new CustomMacaroonHex to lndclient's LndServicesConfig.
  • (2) Update Loop, Pool, Faraday and LiT to use lndclient version containing the above change.
  • (3) Add PR to lnd that adds a new RPC call to check external macaroon permissions, for example CheckMacaroonPermission. The same PR should also add a new flag to the BakeMacaroon RPC that allows specifying permission pairs or URIs that lnd doesn't know. The flag could be called --allow_external_permissions.
  • (4) Add the new stateless-remote mode to the LiT binary and its internal gRPC/REST proxy component. In this mode it wouldn't look for a password in the Authorization header field but expect an lndconnect string there. If such a request comes in and the daemons weren't started yet, it would do so first before forwarding the request.
  • (5) Update the LiT UI code to detect the stateless-remote mode and act accordingly.
@guggero guggero added enhancement New feature or request ui gRPC proxy labels Dec 14, 2020
@orbitalturtle
Copy link
Contributor

Looking into this :)

@Roasbeef
Copy link
Member

Roasbeef commented Jul 28, 2021

Want to follow up here with some more details of a new approach I've been discussing with Graham from voltage in the background.

This applies primarily to a stateless init mode for LiT, where lnd is running in an integrated mode. When lnd is integrated, rather than doing the normal TLS+Macaroon stuff as is, we can instead just use a bufconn in place of the normal RPC connections. This lets us not have to deal w/ TLS certs internally, and it's kind of redundant, given everything is already running in the same memory space.

Next up macaroons, we'll still need them unless we want to disable macaroons all together for the stateless init mode. Given that everything is in the same memory space, we can actually just have lnd re generate the admin macaroon on start up (via the bakery), then pass that using an in-memory channel over to the main LiT initialization logic.

  • Here's where this new logic would need to be inserted: https://github.com/lightninglabs/lightning-terminal/blob/master/terminal.go#L232
  • At this point, lnd has been unlocked, but we haven't yet fully initialized the RPC proxy and LiT web server.
  • One question here is "where do we get the macaroon from"? We could either have lnd always send one for unlock, or we get it from some other external source.

With the above, when running in stateless init mode w/ an integrated lnd mode, no special configuration or macaroon+TLS cert passing is needed. We instead just pass macaroons over to LiT in memory, and everything else should work as expected.

For a mode where lnd is remote, but everything else is integrated, we'd follow the route of the user making a "super macaroon" from lnd, and passing that over config or CLI to LiT.

In terms of the relevant areas to execute the plan for a fully integrated lnd mode, here're some relevant areas:

Finally, lnd itself needs to have a set of bufconn listeners that it uses in order to receive these in-memory RPC requests, but still retain its normal external RPC listeners. We can likely leverage all the existing falafel infra that takes protos and generates stubs that terminate using the bufconn connections.

@gkrizek
Copy link
Contributor

gkrizek commented Jul 29, 2021

Thanks for writing this out @Roasbeef. At a high level this makes sense, just a few clarification questions.

lnd re generate the admin macaroon on start up (via the bakery),

I'm not exactly familiar with this process. Does it create a new admin.macaroon every time or just regenerate the same, existing one? Mainly concerned about bloating the macaroon DB

One question here is "where do we get the macaroon from"? We could either have lnd always send one for unlock, or we get it from some other external source.

Say we went with the lnd always send one for unlock method, how would this work in practice? If we're unlocking LND in the same way it exists today (via API), how would LiT get that admin.macaroon passed into it to then start up Loop, Pool, and Faraday?

For a mode where lnd is remote, but everything else is integrated, we'd follow the route of the user making a "super macaroon" from lnd, and passing that over config or CLI to LiT.

Wouldn't the super admin macaroon be needed in both scenarios? A user would have to use it to authenticate to the Pool or Loop APIs if they wanted to use those. A user would bake a super-admin macaroon in LND then use it to authenticate to Pool if they wanted to hit that API externally. Pool would then would call to LND to validate the macaroon in the request. Here I'm assuming that the LiT UI doesn't need the macaroon to Pool to work. If it does then the user would have to input the macaroon into the LiT UI or something...


While typing this I had an idea. What if this was the flow in integrated mode:

  • User unlocks LND with lncli.
  • LND notifies LiT it's unlocked and/or sends the admin.macaroon to LiT via bufconn.
  • LiT uses that admin.macaroon to bake a super-admin.macaroon.
  • Communication between services like Pool to LND could use either macaroon.
  • Communication from LiT into Pool can use the super-admin macaroon.
  • Communication from User to to Pool API would use the super-admin macaroon.
  • Pool would have to call to LND APIs to validate the macaroon
  • We could allow the user to download the super-admin macaroon from the LiT UI even so it's really simple and we still don't have to write anything to disk.

Edit:

Thinking about this even more; Seems like in this setup we'd still have just two types to run LiT, integrated and remote and either one could be ran as stateless. When running in integrated-stateless, the above changes apply. However when running in remote-stateless, there needs to be a way for LiT to accept an admin macaroon from the user. Perhaps instead of the password field we let them input an admin macaroon for LND. Once input, LiT still bakes the super macaroon like normal but then connects via standard TCP to the remote LND node.

@Roasbeef
Copy link
Member

Roasbeef commented Aug 3, 2021

I'm not exactly familiar with this process. Does it create a new admin.macaroon every time or just regenerate the same, existing one? Mainly concerned about bloating the macaroon DB

So it would just re-create it from scratch, no extra DB persistence needed. What we store is the root macaroon key, which can be used to derive any macaroon.

Say we went with the lnd always send one for unlock method, how would this work in practice?

So it would either send it over the RPC response (kinda like what happens in the stateliness init mode), or it would expose a new package-level method to access it. This would only be accessible if things are running in the same memory space, which they are when the integrated mode is set up (lnd as a package): https://github.com/lightninglabs/lightning-terminal/blob/master/terminal.go#L187

If you follow that link, you'll see that ordering wise, we always start up lnd, then start up all the other sub-systems as they rely on lnd already being active before they can start to communicate w/ it. The startSubservers method is where all the other components are spun up, once again just "importing" them as they've been made to be able to function as standalone packages.

Wouldn't the super admin macaroon be needed in both scenarios? A user would have to use it to authenticate to the Pool or Loop APIs if they wanted to use those.

Yeah in terms of them interacting with things on the command line, or via some other interface, you're right. As far as the current LiT UI, what ends up happening is that we catch the request, then actually read the macaroon on disk, to eventually ask each of the relevant sub-systems to validate it.

In the world of the "super macaroon", though, it wouldn't need to ask each sub-system, as lnd offers the ability to handle auth for each of them.

@orbitalturtle
Copy link
Contributor

Cool stuff @Roasbeef . I've been think about this and just have a couple questions on implementation.

So adding the bufconn listeners to lnd is already supported? We just add a new RPCListener? And I just want to make sure I understand why you mentioned Falafel - we can pass in the listeners the same way that's done with Falafel essentially?

@guggero guggero linked a pull request Nov 10, 2021 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request gRPC proxy ui
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants