-
Notifications
You must be signed in to change notification settings - Fork 896
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
One step toward wallet locking #2925
One step toward wallet locking #2925
Conversation
d5e633b
to
58420b4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think command filtering should be done at this level: I prefer a proxy, perhaps offered by a plugin, which can do more serious enforcement.
But the idea of a command hook is fine, and offers some nice capabilities, however it should be plumbed in at the jsonrpc.c parse_request() level, before command dispatch. A security plugin should whitelist, rather than blacklist: this is safe if unknown commands are added (eg. plugins).
What more could be done ? (To get ideas)
I'll amend this to call the hook at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should call the hook with the RPC call payload on all calls, instead of making an arbitrary distinction between sensitive and non-sensitive. That'd give more power to the plugins, while also allowing us not to have to decide on behalf of the user. Putting the call directly in the JSON-RPC dispatch in parse_request seems like a good place.
One thing that I don't quite get is why this has to be synchronous in the first place. Since the internal dispatch will not be called until we get the result of the hook we can just call the hook asynchronously (we might even be able to call it before we start a new DB transaction, hence removing the split transaction across DB transaction issue). Don't get me wrong, I love the generalization of the sync hook call, but I'm wondering whether we should make use of it here.
If we do it asynchronously, we can also get some of the features @ZmnSCPxj wants: upon receiving a call that we'd like to rewrite, we will just call the rewritten RPC ourselves (us being the plugin) and then directly return the result as the result of the original call. No need to intercept the original call, rewrite, reinject into the dispatch, and then have the dispatch return the result for the rewritten call.
To be really honest, I first expected hooks to be asynchronous and had a hard time debugging. I then understood that I would (in the "command is restricted" case) call
In that case, what would happen to the ongoing request that called us ? Would we return a |
They were, but somewhere along the way they were made asynchronous, which is really strange and contradicts the documentation, but may be a blessing since it actually allows us to make RPC calls which would otherwise not be possible. I am almost convinced now that only making hooks synchronous (read:
The dispatch code in We have a couple of similar situations (basically everywhere a JSON-RPC call results in further queries to other daemons such as lightning/lightningd/gossip_control.c Lines 258 to 274 in 69b7ef1
We start processing the call, then issue the followup query that will eventually return the results (this is equivalent to the hook-call) and signal that the RPC call is pending using the call to |
Thank you for the detailed explanation, it makes more sense, I'll try it that way. |
Added the state::shelved tag to signal that we may be trying another solution, but don't want to close this PR right away 😉 |
I'm still working on this, I've just started something else (almost finished) that I wanted to finish before coming back to this. |
No problem, I'm just tagging these so I don't get the feeling that our PR stack is growing and I can stop checking this one every day 😉 |
5bcc8e7
to
8665f3b
Compare
0b8a383
to
8ed91a4
Compare
Finally got the time to fix this. Updated the OP. |
0e98e30
to
350b294
Compare
Thanks for the review ! Fixed the |
ACK 350b294 |
@rustyrussell is your request for changes still valid? If not I'll merge this 😉 |
350b294
to
2d7c4d6
Compare
Forgot to add documentation for the hook, documented it and rebased on top of master. By the way @cdecker about the caller authentication, is there any drawback in making the authentication field part of the
EDIT: For the |
The downside of that would be that we are mixing call parameters and authentication mechanism, whereas they really are orthogonal. It's certainly doable, by having the client inject authentication parameters and the auth plugin filtering them out again, but it feels really awkward. |
Ok, I think I'll go for a violation of the JSONRPC specs then :D |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack 2d7c4d6
This is really neat! I'll work on getting rid of that annoying timer ;)
json_add_tok(result, json_strdup(tmpctx, buffer, t), t, buffer); | ||
json_object_end(result); | ||
break; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, since tok->type should always be one of these types, change these breaks to returns, except the last JSMN_UNDEFINED. Then abort() at the bottom of the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, a JSMN_UNDEFINED
tok basically means "bad json" ?
diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c
index 2562d7627..a68ffde2c 100644
--- a/lightningd/jsonrpc.c
+++ b/lightningd/jsonrpc.c
@@ -775,11 +775,12 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[])
/* Duplicate since we might outlive the connection */
rpc_hook->buffer = tal_dup_arr(rpc_hook, char, jcon->buffer, tal_count(jcon->buffer), 0);
rpc_hook->request = tal_dup_arr(rpc_hook, const jsmntok_t, tok, tal_count(tok), 0);
- /* Prevent a race between was_pending and still_pending */
- new_reltimer(c->ld->timers, rpc_hook, time_from_msec(1),
- call_rpc_command_hook, rpc_hook);
+ call_rpc_command_hook(rpc_hook);
- return command_still_pending(c);
+ /* Could have already completed the request, or it could be pending.
+ * Hard to plumb through the layers of callbacks, and caller doesn't
+ * care. */
+ return NULL;
}
/* Mutual recursion */ |
8b31bda
to
38d77bc
Compare
Thanks for the patch ! Replaced the cherry-picking of fields by a |
491a11a
to
3d0dac2
Compare
Ok so I removed your patch because otherwise valgrind tests would not pass if there is no plugin registered for the Hope the timer isn't just hiding the stuff to both me and valgrind ?... |
The 'rpc_command' hook allows a plugin to take over any RPC command. It sends the complete JSONRPC request to the plugin, which can then respond with : - {'continue'}: executes the command normally - {'replace': {a_jsonrpc_request}}: replaces the request made - {'return': {'result': {}}}: send a custom response - {'return': {'error': {}}}: send a custom error This way, a plugin can modify (/reimplement) or restrict the usage of any of `lightningd`'s commands. Changelog-Added: Plugin: A new plugin hook, `rpc_command` allows a plugin to take over `lightningd` for any RPC command.
3d0dac2
to
640f949
Compare
Changed the bad json in the "continue" case. Here is the patch diff diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c
index 4e534d242..82c445f3e 100644
--- a/lightningd/jsonrpc.c
+++ b/lightningd/jsonrpc.c
@@ -623,16 +623,23 @@ static void
rpc_command_hook_callback(struct rpc_command_hook_payload *p,
const char *buffer, const jsmntok_t *resulttok)
{
- const jsmntok_t *tok, *method, *params, *custom_return;
+ const jsmntok_t *tok, *method, *params, *custom_return, *tok_continue;
struct json_stream *response;
+ bool exec;
params = json_get_member(p->buffer, p->request, "params");
/* If no plugin registered, just continue command execution. Same if
* the registered plugin tells us to do so. */
- if (buffer == NULL || json_tok_streq(buffer, resulttok, "continue"))
- return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer,
+ if (buffer == NULL)
+ return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer,
p->request, params));
+ else {
+ tok_continue = json_get_member(buffer, resulttok, "continue");
+ if (tok_continue && json_to_bool(buffer, tok_continue, &exec) && exec)
+ return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer,
+ p->request, params));
+ }
/* If the registered plugin did not respond with continue,
* it wants either to replace the request... */ |
ACK 640f949 |
[updated 09/27]
This adds a new
sensitive_command
hook, which allows a plugin to take over any RPC command execution.This was at first aimed to only restrict the use of sensitive commands, but was made a general purpose RPC hook as suggested by @ZmnSCPxj (#2925 (comment)), it now :
Going further, and off topic of the wallet locking, a plugin registering for the
rpc_command
hook could take over theplugin start
(and cie.) command and extend the plugins possibilities, such as permitting a workaround for hook chaining (#2796).For example, the plugin registering for
rpc_command
could register forhtlc_accepted
and overrideplugin start
, then ask to every new plugin started withplugin start
and registering this same hook the permission to accept the HTLC before returning the response tolightningd
.[/updated 09/27]
Next step forward
I plan to make a (static) plugin (to be added here) which would allow users to set up a passphrase and to lock / unlock the sensitive RPC calls (
a little bit likecompletely like bitcoin-core'swalletpassphrase
mechanism).I prefered to separate this into multiple PRs.
Another step forward
If we encrypt
hsm_secret
then even islightningd
is stopped somehow (likely killed) it cannot be restarted without the locking plugin. I don't know exactly how to achieve this final step, or if it is even a good scheme ?