From a903208121128e99a9ebb6422f0aab6f7572239f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 3 Aug 2021 16:54:11 +0930 Subject: [PATCH] commando: don't let readonly default read the datastore. That... would be dumb, since it holds the master secret! Signed-off-by: Rusty Russell --- commando/README.md | 2 +- commando/commando.py | 2 ++ commando/test_commando.py | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/commando/README.md b/commando/README.md index e871627..09aa507 100644 --- a/commando/README.md +++ b/commando/README.md @@ -20,7 +20,7 @@ For general plugin installation instructions see the repos main There are two configuration options, which can be specified multiple times: -* --commando-reader: a node id which can execute `list` and `get` / `summary` commands +* --commando-reader: a node id which can execute (most) `list` and `get` / `summary` commands * --commando-writer: a node id which can execute any commands. You can do this for static access lists, no runes necessary. You would diff --git a/commando/commando.py b/commando/commando.py index b3e2c65..d2ccf38 100755 --- a/commando/commando.py +++ b/commando/commando.py @@ -231,6 +231,8 @@ def add_reader_restrictions(rune: runes.Rune) -> str: '|method=summary')) # But not getsharesecret! rune.add_restriction(runes.Restriction.from_str('method/getsharedsecret')) + # And not listdatastore! + rune.add_restriction(runes.Restriction.from_str('method/listdatastore')) return rune.to_base64() diff --git a/commando/test_commando.py b/commando/test_commando.py index 612b21c..d873df1 100755 --- a/commando/test_commando.py +++ b/commando/test_commando.py @@ -283,3 +283,26 @@ def test_rune_time(node_factory): method='commando-rune', rune=rune, params={'restrictions': 'id={}'.format(l2.info['id'])}) + + +def test_readonly(node_factory): + l1, l2 = node_factory.line_graph(2, fundchannel=False, + opts={'plugin': [plugin_path, + datastore_path]}) + rrune = l2.rpc.commando_rune(restrictions='readonly')['rune'] + + l1.rpc.call(method='commando', + payload={'peer_id': l2.info['id'], + 'method': 'listchannels', + 'rune': rrune, + 'params': {'source': l1.info['id']}}) + + with pytest.raises(RpcError, match='Not authorized.* = getsharedsecret'): + l1.rpc.commando(peer_id=l2.info['id'], + rune=rrune, + method='getsharedsecret') + + with pytest.raises(RpcError, match='Not authorized.* = listdatastore'): + l1.rpc.commando(peer_id=l2.info['id'], + rune=rrune, + method='listdatastore')