Skip to content

Commit

Permalink
clnrest: prefixing all rest config options with cln
Browse files Browse the repository at this point in the history
This will allow users to use clnrest with c-lightning-REST without conflicts.
It was required for applications to have enough time for migrating from c-lightning-REST to clnrest.

Changelog-Changed:
config option `rest-certs` changed to `clnrest-certs`
config option `rest-protocol` changed to `clnrest-protocol`
config option `rest-host` changed to `clnrest-host`
config option `rest-port` changed to `clnrest-port`
config option `rest-cors-origins` changed to `clnrest-cors-origins`
config option `rest-csp` changed to `clnrest-csp`
  • Loading branch information
ShahanaFarooqui authored and nepet committed Nov 16, 2023
1 parent 017581c commit cc2665d
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 68 deletions.
29 changes: 15 additions & 14 deletions doc/developers-guide/app-development/rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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])
```

Expand Down
12 changes: 6 additions & 6 deletions doc/lightningd-config.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';`.

Expand Down
2 changes: 1 addition & 1 deletion plugins/clnrest/clnrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 6 additions & 6 deletions plugins/clnrest/utilities/rpc_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
22 changes: 11 additions & 11 deletions plugins/clnrest/utilities/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
60 changes: 30 additions & 30 deletions tests/test_clnrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -73,21 +73,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',
Expand Down Expand Up @@ -131,7 +131,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
Expand Down Expand Up @@ -374,49 +374,49 @@ 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'] == '*'
# This might happen really early!
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!
Expand Down

0 comments on commit cc2665d

Please sign in to comment.