diff --git a/doc/developers-guide/app-development/rest.md b/doc/developers-guide/app-development/rest.md index 4744938e0e63..3bb321ddf47e 100644 --- a/doc/developers-guide/app-development/rest.md +++ b/doc/developers-guide/app-development/rest.md @@ -28,7 +28,7 @@ An online demo for the REST interface is available at [REST API REFERENCE](ref:g > > - The `ip` should be configured with your system's public IP address. > -> - Default `rest-host` is `127.0.0.1` but this testing will require it to be `0.0.0.0`. +> - Default `clnrest-host` is `127.0.0.1` but this testing will require it to be `0.0.0.0`. > > Note: This setup is for **testing only**. It is **highly recommended** to test with _non-mainnet_ (regtest/testnet) setup only. @@ -38,36 +38,37 @@ An online demo for the REST interface is available at [REST API REFERENCE](ref:g The plugin is built-in with Core Lightning but its python dependencies are not, and must be installed separately. Install required packages with `pip install -r plugins/clnrest/requirements.txt`. -Note: if you have the older c-lightning-rest plugin, you can configure Core Lightning with `disable-plugin=clnrest.py` option -to avoid any conflict with this one. Of course, you could use this one instead! +Note: if you have the older c-lightning-REST plugin, you can configure Core Lightning with `disable-plugin=clnrest.py` +option to avoid confusion with this one. You can also run both plugins simultaneously till all your applications +are not migrated to `clnrest`. ## Configuration -If `rest-port` is not specified, the plugin will disable itself. +If `clnrest-port` is not specified, the plugin will disable itself. -- --rest-port: Sets the REST server port to listen to (3010 is common) +- --clnrest-port: Sets the REST server port to listen to (3010 is common) -- --rest-protocol: Specifies the REST server protocol. Default is HTTPS. +- --clnrest-protocol: Specifies the REST server protocol. Default is HTTPS. -- --rest-host: Defines the REST server host. Default is 127.0.0.1. +- --clnrest-host: Defines the REST server host. Default is 127.0.0.1. -- --rest-certs: Defines the path for HTTPS cert & key. Default path is same as RPC file path to utilize gRPC's client certificate. +- --clnrest-certs: Defines the path for HTTPS cert & key. Default path is same as RPC file path to utilize gRPC's client certificate. If it is missing at the configured location, new identity will be generated. -- --rest-csp: Creates a whitelist of trusted content sources that can run on a webpage and helps mitigate the risk of attacks. +- --clnrest-csp: Creates a whitelist of trusted content sources that can run on a webpage and helps mitigate the risk of attacks. Default CSP: `default-src 'self'; font-src 'self'; img-src 'self' data:; frame-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';` Example CSP: -`rest-csp=default-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'; style-src 'self'; script-src 'self';`. +`clnrest-csp=default-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'; style-src 'self'; script-src 'self';`. -- --rest-cors-origins: Define multiple origins which are allowed to share resources on web pages to a domain different from the +- --clnrest-cors-origins: Define multiple origins which are allowed to share resources on web pages to a domain different from the one that served the web page. Default is `*` which allows all origins. Example to define multiple origins: ``` -rest-cors-origins=https://localhost:5500 -rest-cors-origins=http://192.168.1.50:3030 -rest-cors-origins=https?://127.0.0.1:([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]) +clnrest-cors-origins=https://localhost:5500 +clnrest-cors-origins=http://192.168.1.50:3030 +clnrest-cors-origins=https?://127.0.0.1:([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]) ``` diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md index 4347d9c11dff..8a039ac1d789 100644 --- a/doc/lightningd-config.5.md +++ b/doc/lightningd-config.5.md @@ -632,27 +632,27 @@ all DNS lookups, to avoid leaking information. Set a Tor control password, which may be needed for *autotor:* to authenticate to the Tor control port. -* **rest-port**=*PORT* [plugin `clnrest.py`] +* **clnrest-port**=*PORT* [plugin `clnrest.py`] Sets the REST server port to listen to (3010 is common). If this is not specified, the clnrest.py plugin will be disabled. -* **rest-protocol**=*PROTOCOL* [plugin `clnrest.py`] +* **clnrest-protocol**=*PROTOCOL* [plugin `clnrest.py`] Specifies the REST server protocol. Default is HTTPS. -* **rest-host**=*HOST* [plugin `clnrest.py`] +* **clnrest-host**=*HOST* [plugin `clnrest.py`] Defines the REST server host. Default is 127.0.0.1. -* **rest-certs**=*PATH* [plugin `clnrest.py`] +* **clnrest-certs**=*PATH* [plugin `clnrest.py`] Defines the path for HTTPS cert & key. Default path is same as RPC file path to utilize gRPC's client certificate. If it is missing at the configured location, new identity (`client.pem` and `client-key.pem`) will be generated. -* **rest-cors-origins**=*CORSORIGINS* [plugin `clnrest.py`] +* **clnrest-cors-origins**=*CORSORIGINS* [plugin `clnrest.py`] Define multiple origins which are allowed to share resources on web pages to a domain different from the one that served the web page. Default is `*` which allows all origins. -* **rest-csp**=*CSPOLICY* [plugin `clnrest.py`] +* **clnrest-csp**=*CSPOLICY* [plugin `clnrest.py`] Creates a whitelist of trusted content sources that can run on a webpage and helps mitigate the risk of attacks. Default CSP is `default-src 'self'; font-src 'self'; img-src 'self' data:; frame-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';`. diff --git a/plugins/clnrest/clnrest.py b/plugins/clnrest/clnrest.py index 889ff09d684f..9da124e513d3 100755 --- a/plugins/clnrest/clnrest.py +++ b/plugins/clnrest/clnrest.py @@ -119,7 +119,7 @@ def add_csp_headers(response): response.headers['Content-Security-Policy'] = REST_CSP.replace('\\', '').replace("[\"", '').replace("\"]", '') return response except Exception as err: - plugin.log(f"Error from rest-csp config: {err}", "info") + plugin.log(f"Error from clnrest-csp config: {err}", "info") def set_application_options(plugin): diff --git a/plugins/clnrest/utilities/rpc_plugin.py b/plugins/clnrest/utilities/rpc_plugin.py index 3266d2ff01e5..fbb0019b324b 100644 --- a/plugins/clnrest/utilities/rpc_plugin.py +++ b/plugins/clnrest/utilities/rpc_plugin.py @@ -3,9 +3,9 @@ plugin = Plugin(autopatch=False) -plugin.add_option(name="rest-certs", default=os.getcwd(), description="Path for certificates (for https)", opt_type="string", deprecated=False) -plugin.add_option(name="rest-protocol", default="https", description="REST server protocol", opt_type="string", deprecated=False) -plugin.add_option(name="rest-host", default="127.0.0.1", description="REST server host", opt_type="string", deprecated=False) -plugin.add_option(name="rest-port", default=None, description="REST server port to listen", opt_type="int", deprecated=False) -plugin.add_option(name="rest-cors-origins", default="*", description="Cross origin resource sharing origins", opt_type="string", deprecated=False, multi=True) -plugin.add_option(name="rest-csp", default="default-src 'self'; font-src 'self'; img-src 'self' data:; frame-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';", description="Content security policy (CSP) for the server", opt_type="string", deprecated=False, multi=False) +plugin.add_option(name="clnrest-certs", default=os.getcwd(), description="Path for certificates (for https)", opt_type="string", deprecated=False) +plugin.add_option(name="clnrest-protocol", default="https", description="REST server protocol", opt_type="string", deprecated=False) +plugin.add_option(name="clnrest-host", default="127.0.0.1", description="REST server host", opt_type="string", deprecated=False) +plugin.add_option(name="clnrest-port", default=None, description="REST server port to listen", opt_type="int", deprecated=False) +plugin.add_option(name="clnrest-cors-origins", default="*", description="Cross origin resource sharing origins", opt_type="string", deprecated=False, multi=True) +plugin.add_option(name="clnrest-csp", default="default-src 'self'; font-src 'self'; img-src 'self' data:; frame-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';", description="Content security policy (CSP) for the server", opt_type="string", deprecated=False, multi=False) diff --git a/plugins/clnrest/utilities/shared.py b/plugins/clnrest/utilities/shared.py index f58e0026d2f6..1f0acd569c47 100644 --- a/plugins/clnrest/utilities/shared.py +++ b/plugins/clnrest/utilities/shared.py @@ -39,25 +39,25 @@ def validate_port(port): def set_config(options): - if 'rest-port' not in options: - return "`rest-port` option is not configured" + if 'clnrest-port' not in options: + return "`clnrest-port` option is not configured" global CERTS_PATH, REST_PROTOCOL, REST_HOST, REST_PORT, REST_CSP, REST_CORS_ORIGINS - REST_PORT = int(options["rest-port"]) + REST_PORT = int(options["clnrest-port"]) if validate_port(REST_PORT) is False: - return f"`rest-port` {REST_PORT}, should be a valid available port between 1024 and 65535." + return f"`clnrest-port` {REST_PORT}, should be a valid available port between 1024 and 65535." - REST_HOST = str(options["rest-host"]) + REST_HOST = str(options["clnrest-host"]) if REST_HOST != "localhost" and validate_ip4(REST_HOST) is False and validate_ip6(REST_HOST) is False: - return f"`rest-host` should be a valid IP." + return f"`clnrest-host` should be a valid IP." - REST_PROTOCOL = str(options["rest-protocol"]) + REST_PROTOCOL = str(options["clnrest-protocol"]) if REST_PROTOCOL != "http" and REST_PROTOCOL != "https": - return f"`rest-protocol` can either be http or https." + return f"`clnrest-protocol` can either be http or https." - CERTS_PATH = str(options["rest-certs"]) - REST_CSP = str(options["rest-csp"]) - cors_origins = options["rest-cors-origins"] + CERTS_PATH = str(options["clnrest-certs"]) + REST_CSP = str(options["clnrest-csp"]) + cors_origins = options["clnrest-cors-origins"] REST_CORS_ORIGINS.clear() for origin in cors_origins: REST_CORS_ORIGINS.append(str(origin)) diff --git a/tests/test_clnrest.py b/tests/test_clnrest.py index fedf42166862..45156a010769 100644 --- a/tests/test_clnrest.py +++ b/tests/test_clnrest.py @@ -24,22 +24,22 @@ def http_session_with_retry(): def test_clnrest_no_auto_start(node_factory): - """Ensure that we do not start clnrest unless a `rest-port` is configured.""" + """Ensure that we do not start clnrest unless a `clnrest-port` is configured.""" l1 = node_factory.get_node() # This might happen really early! l1.daemon.logsearch_start = 0 assert [p for p in l1.rpc.plugin('list')['plugins'] if 'clnrest.py' in p['name']] == [] - assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `rest-port` option is not configured') + assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-port` option is not configured') def test_clnrest_self_signed_certificates(node_factory): - """Test that self-signed certificates have `rest-host` IP in Subject Alternative Name.""" + """Test that self-signed certificates have `clnrest-host` IP in Subject Alternative Name.""" rest_port = str(reserve()) rest_host = '127.0.0.1' base_url = f'https://{rest_host}:{rest_port}' l1 = node_factory.get_node(options={'disable-plugin': 'cln-grpc', - 'rest-port': rest_port, - 'rest-host': rest_host}) + 'clnrest-port': rest_port, + 'clnrest-host': rest_host}) # This might happen really early! l1.daemon.logsearch_start = 0 l1.daemon.wait_for_log(r'plugin-clnrest.py: REST server running at ' + base_url) @@ -54,12 +54,12 @@ def test_clnrest_self_signed_certificates(node_factory): def test_clnrest_uses_grpc_plugin_certificates(node_factory): """Test that clnrest reuses `cln-grpc` plugin certificates if available. Defaults: - - rest-protocol: https + - clnrest-protocol: https """ rest_host = 'localhost' grpc_port = str(reserve()) rest_port = str(reserve()) - l1 = node_factory.get_node(options={'grpc-port': grpc_port, 'rest-host': rest_host, 'rest-port': rest_port}) + l1 = node_factory.get_node(options={'grpc-port': grpc_port, 'clnrest-host': rest_host, 'clnrest-port': rest_port}) base_url = f'https://{rest_host}:{rest_port}' # This might happen really early! l1.daemon.logsearch_start = 0 @@ -75,21 +75,21 @@ def test_clnrest_uses_grpc_plugin_certificates(node_factory): def test_clnrest_generate_certificate(node_factory): """Test whether we correctly generate the certificates.""" - # when `rest-protocol` is `http`, certs are not generated at `rest-certs` path + # when `clnrest-protocol` is `http`, certs are not generated at `clnrest-certs` path rest_port = str(reserve()) rest_protocol = 'http' rest_certs = node_factory.directory + '/clnrest-certs' - l1 = node_factory.get_node(options={'rest-port': rest_port, - 'rest-protocol': rest_protocol, - 'rest-certs': rest_certs}) + l1 = node_factory.get_node(options={'clnrest-port': rest_port, + 'clnrest-protocol': rest_protocol, + 'clnrest-certs': rest_certs}) assert not Path(rest_certs).exists() # node l1 not started rest_port = str(reserve()) rest_certs = node_factory.directory + '/clnrest-certs' - l1 = node_factory.get_node(options={'rest-port': rest_port, - 'rest-certs': rest_certs}, start=False) + l1 = node_factory.get_node(options={'clnrest-port': rest_port, + 'clnrest-certs': rest_certs}, start=False) rest_certs_path = Path(rest_certs) files = [rest_certs_path / f for f in [ 'ca.pem', @@ -143,7 +143,7 @@ def start_node_with_clnrest(node_factory): - the certificate authority path used for the self-signed certificates.""" rest_port = str(reserve()) rest_certs = node_factory.directory + '/clnrest-certs' - l1 = node_factory.get_node(options={'rest-port': rest_port, 'rest-certs': rest_certs}) + l1 = node_factory.get_node(options={'clnrest-port': rest_port, 'clnrest-certs': rest_certs}) base_url = 'https://127.0.0.1:' + rest_port # This might happen really early! l1.daemon.logsearch_start = 0 @@ -386,34 +386,34 @@ def test_clnrest_websocket_rune_no_listnotifications(node_factory): def test_clnrest_options(node_factory): - """Test startup options `rest-host`, `rest-protocol` and `rest-certs`.""" + """Test startup options `clnrest-host`, `clnrest-protocol` and `clnrest-certs`.""" # with invalid port rest_port = 1000 - l1 = node_factory.get_node(options={'rest-port': rest_port}) - assert l1.daemon.is_in_log(f'plugin-clnrest.py: Killing plugin: disabled itself at init: `rest-port` {rest_port}, should be a valid available port between 1024 and 65535.') + l1 = node_factory.get_node(options={'clnrest-port': rest_port}) + assert l1.daemon.is_in_log(f'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-port` {rest_port}, should be a valid available port between 1024 and 65535.') # with invalid protocol rest_port = str(reserve()) rest_protocol = 'htttps' - l1 = node_factory.get_node(options={'rest-port': rest_port, - 'rest-protocol': rest_protocol}) - assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `rest-protocol` can either be http or https.') + l1 = node_factory.get_node(options={'clnrest-port': rest_port, + 'clnrest-protocol': rest_protocol}) + assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-protocol` can either be http or https.') # with invalid host rest_port = str(reserve()) rest_host = '127.0.0.12.15' - l1 = node_factory.get_node(options={'rest-port': rest_port, - 'rest-host': rest_host}) - assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `rest-host` should be a valid IP.') + l1 = node_factory.get_node(options={'clnrest-port': rest_port, + 'clnrest-host': rest_host}) + assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-host` should be a valid IP.') def test_clnrest_http_headers(node_factory): - """Test HTTP headers set with `rest-csp` and `rest-cors-origins` options.""" + """Test HTTP headers set with `clnrest-csp` and `clnrest-cors-origins` options.""" # start a node with clnrest l1, base_url, ca_cert = start_node_with_clnrest(node_factory) http_session = http_session_with_retry() - # Default values for `rest-csp` and `rest-cors-origins` options + # Default values for `clnrest-csp` and `clnrest-cors-origins` options response = http_session.get(base_url + '/v1/list-methods', verify=ca_cert) assert response.headers['Content-Security-Policy'] == "default-src 'self'; font-src 'self'; img-src 'self' data:; frame-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';" assert response.headers['Access-Control-Allow-Origin'] == '*' @@ -421,14 +421,14 @@ def test_clnrest_http_headers(node_factory): l1.daemon.logsearch_start = 0 l1.daemon.wait_for_log(f'plugin-clnrest.py: REST server running at {base_url}') - # Custom values for `rest-csp` and `rest-cors-origins` options + # Custom values for `clnrest-csp` and `clnrest-cors-origins` options rest_port = str(reserve()) rest_certs = node_factory.directory + '/clnrest-certs' l2 = node_factory.get_node(options={ - 'rest-port': rest_port, - 'rest-certs': rest_certs, - 'rest-csp': "default-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'; style-src 'self'; script-src 'self';", - 'rest-cors-origins': ['https://localhost:5500', 'http://192.168.1.30:3030', 'http://192.168.1.10:1010'] + 'clnrest-port': rest_port, + 'clnrest-certs': rest_certs, + 'clnrest-csp': "default-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'; style-src 'self'; script-src 'self';", + 'clnrest-cors-origins': ['https://localhost:5500', 'http://192.168.1.30:3030', 'http://192.168.1.10:1010'] }) base_url = 'https://127.0.0.1:' + rest_port # This might happen really early!