config: add the ability for plugins to specify that config values should be concealed.

And use it for `exposesecret-passphrase`.  This is probably overly
cautious, but it makes me feel a little better that we won't leak it
to someone with read-only access.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-09-06 15:51:52 +09:30
parent 101aeea52c
commit 8293352425
8 changed files with 24 additions and 8 deletions

View File

@ -62,6 +62,8 @@ struct configvar {
#define OPT_DYNAMIC (1 << (OPT_USER_START+6)) #define OPT_DYNAMIC (1 << (OPT_USER_START+6))
/* Keep whitespace at the end of the option argument */ /* Keep whitespace at the end of the option argument */
#define OPT_KEEP_WHITESPACE (1 << (OPT_USER_START+7)) #define OPT_KEEP_WHITESPACE (1 << (OPT_USER_START+7))
/* Don't show value in listconfigs */
#define OPT_CONCEAL (1 << (OPT_USER_START+8))
/* Use this instead of opt_register_*_arg if you want OPT_* from above */ /* Use this instead of opt_register_*_arg if you want OPT_* from above */
#define clnopt_witharg(names, type, cb, show, arg, desc) \ #define clnopt_witharg(names, type, cb, show, arg, desc) \

View File

@ -124,10 +124,11 @@ Plugins are free to register any `name` for their `rpcmethod` as long as the nam
There are currently four supported option 'types': There are currently four supported option 'types':
- string: a string - `string`: a string
- bool: a boolean - `string-conceal`: a string which will appear as "..." in `listconfigs`.
- int: parsed as a signed integer (64-bit) - `bool`: a boolean
- flag: no-arg flag option. Presented as `true` if config specifies it. - `int`: parsed as a signed integer (64-bit)
- `flag`: no-arg flag option. Presented as `true` if config specifies it.
In addition, string and int types can specify `"multi": true` to indicate they can be specified multiple times. These will always be represented in `init` as a (possibly empty) JSON array. "multi" flag types do not make In addition, string and int types can specify `"multi": true` to indicate they can be specified multiple times. These will always be represented in `init` as a (possibly empty) JSON array. "multi" flag types do not make
sense. sense.

View File

@ -65,6 +65,9 @@ static const char *get_opt_val(const struct opt_table *ot,
char buf[], char buf[],
const struct configvar *cv) const struct configvar *cv)
{ {
if (ot->type & OPT_CONCEAL)
return "...";
if (ot->show == (void *)opt_show_charp) { if (ot->show == (void *)opt_show_charp) {
/* Don't truncate or quote! */ /* Don't truncate or quote! */
return *(char **)ot->u.carg; return *(char **)ot->u.carg;

View File

@ -2095,8 +2095,10 @@ void add_config_deprecated(struct lightningd *ld,
if (!opt->show(buf, sizeof(buf) - sizeof("..."), opt->u.carg)) if (!opt->show(buf, sizeof(buf) - sizeof("..."), opt->u.carg))
buf[0] = '\0'; buf[0] = '\0';
if ((opt->type & OPT_SHOWINT) if (opt->type & OPT_CONCEAL) {
|| (opt->type & OPT_SHOWMSATS)) { strcpy(buf, "...");
} else if ((opt->type & OPT_SHOWINT)
|| (opt->type & OPT_SHOWMSATS)) {
if (streq(buf, "") if (streq(buf, "")
|| strspn(buf, "-0123456789.") != strlen(buf)) || strspn(buf, "-0123456789.") != strlen(buf))
errx(1, "Bad literal for %s: %s", name0, buf); errx(1, "Bad literal for %s: %s", name0, buf);

View File

@ -1168,7 +1168,10 @@ static const char *plugin_opt_add(struct plugin *plugin, const char *buffer,
/* These all take an arg. */ /* These all take an arg. */
char *(*cb_arg)(const char *optarg, void *arg); char *(*cb_arg)(const char *optarg, void *arg);
if (json_tok_streq(buffer, typetok, "string")) { if (json_tok_streq(buffer, typetok, "string-conceal")) {
optflags |= OPT_CONCEAL;
cb_arg = (void *)plugin_opt_string_check;
} else if (json_tok_streq(buffer, typetok, "string")) {
cb_arg = (void *)plugin_opt_string_check; cb_arg = (void *)plugin_opt_string_check;
} else if (json_tok_streq(buffer, typetok, "int")) { } else if (json_tok_streq(buffer, typetok, "int")) {
cb_arg = (void *)plugin_opt_long_check; cb_arg = (void *)plugin_opt_long_check;

1
plugins/.gitignore vendored
View File

@ -18,3 +18,4 @@ cln-renepay
recover recover
cln-askrene cln-askrene
recklessrpc recklessrpc
exposesecret

View File

@ -156,7 +156,7 @@ int main(int argc, char *argv[])
plugin_main(argv, init, take(exposesecret), plugin_main(argv, init, take(exposesecret),
PLUGIN_RESTARTABLE, true, NULL, commands, ARRAY_SIZE(commands), PLUGIN_RESTARTABLE, true, NULL, commands, ARRAY_SIZE(commands),
NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0,
plugin_option("exposesecret-passphrase", "string", plugin_option("exposesecret-passphrase", "string-conceal",
"Enable exposesecret command to allow HSM Secret backup, with this passphrase", "Enable exposesecret command to allow HSM Secret backup, with this passphrase",
charp_option, NULL, &exposesecret->exposure_passphrase), charp_option, NULL, &exposesecret->exposure_passphrase),
NULL); NULL);

View File

@ -4453,6 +4453,10 @@ def test_listchannels_broken_message(node_factory):
def test_exposesecret(node_factory): def test_exposesecret(node_factory):
l1, l2 = node_factory.get_nodes(2, opts=[{'exposesecret-passphrase': "test_exposesecret"}, {}]) l1, l2 = node_factory.get_nodes(2, opts=[{'exposesecret-passphrase': "test_exposesecret"}, {}])
# listconfigs will conceal the value for us, even if we ask directly.
l1.rpc.listconfigs()['configs']['exposesecret-passphrase']['value_str'] == '...'
l1.rpc.listconfigs('exposesecret-passphrase')['configs']['exposesecret-passphrase']['value_str'] == '...'
# l2 won't expose the secret! # l2 won't expose the secret!
with pytest.raises(RpcError, match="exposesecrets-passphrase is not set"): with pytest.raises(RpcError, match="exposesecrets-passphrase is not set"):
l2.rpc.exposesecret(passphrase='test_exposesecret') l2.rpc.exposesecret(passphrase='test_exposesecret')