Skip to content
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

Add Native SSL Support #1026

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ Example RTL-Config.json:
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"ssl": {
"key": null,
"cert": null,
"ca": null,
"altIp": "127.0.0.1",
"commonName": "localhost",
"countryName": "US",
"encryptionBits": 2048,
"stateName": "Florida",
"localityName": "Miami",
"organizationName": "RTL",
"organizationalUnit": "RTL",
"validForYears": 10,
"rejectUnauthorized": true,
"requestCert": false
},
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
Expand Down Expand Up @@ -173,7 +189,7 @@ E.g. if the IP address of your node is 192.168.0.15 then open your browser at th
3. Config tweaks for running RTL server and LND on separate devices on the same network can be found [here](./docs/RTL_setups.md).

4. Any Other setup: **Please be advised, if you are accessing your node remotely via RTL, its critical to encrypt the communication via use of https. You can use solutions like nginx and letsencrypt or TOR to setup secure access for RTL.**
- Sample SSL setup guide can be found [here](./docs/RTL_SSL_setup.md)
- RTL now supports native SSL! See config options [here](./docs/Application_configurations.md). Alternatively, you can run nginx as an SSL proxy. An example nginx SSL setup guide can be found [here](./docs/RTL_SSL_setup.md)
- (For advanced users) A sample SSL guide to serve remote access over an encrypted Tor connection can be found [here](./docs/RTL_TOR_setup.md)

### <a name="trouble"></a>Troubleshooting
Expand Down
22 changes: 20 additions & 2 deletions .github/docs/Application_configurations.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
RTL allows the user to configure and control specific application parameters for app customization and integration.<br />
The parameters can be configured via RTL-Config.json file or through environment variables defined at the OS level. Required <br />
parameters have `default` values for initial setup and can be updated after RTL server initial start.<br />
The parameters can be configured via RTL-Config.json file or through environment variables defined at the OS level.<br />
Required parameters have `default` values for initial setup and can be updated after RTL server initial start.<br />
SSL config parameter can be set as `undefined`, `true` (which uses self-signed certs), `false` (http only), or an `<object>` as outlined below. `key`, `cert` and `ca` parameters are reserved for static file paths to valid SSL key files. If `key`, `cert`, or `ca` are used, no self-signed certificate options will be used. `rejectUnauthorized` and `requestCert` can be used in all cases to enforce x509 certificate validation. <br />
<br />
### RTL-Config.json<br />
```
Expand All @@ -9,6 +10,22 @@ parameters have `default` values for initial setup and can be updated after RTL
"port": "<port number for the rtl node server, default '3000', Required>",
"host": "<host for the rtl node server, default 'all IPs', Optional>",
"defaultNodeIndex": <Default index to load when rtl server starts, default 1, Optional>,
"ssl": {
"key": "<Full path of a server SSL key file to enable SSL, default is null, Optional>",
"cert": "<Full path of a server SSL pem format certificate file to enable SSL, default is null, Optional>",
"ca": <Full path of a server SSL certificate authority file to enable SSL, default is null, Optional>,
"altIp": "<parameter to set an alternate IP address in any randomly generated SSL certificate, default '127.0.0.1', Optional>",
"commonName": "<parameter to set a common name in any randomly generated SSL certificate, default 'localhost', Optional>",
"countryName": "<parameter to set a country name in any randomly generated SSL certificate, default 'US', Optional>",
"stateName": "<parameter to set a state name in any randomly generated SSL certificate, default 'New York', Optional>",
"localityName": "<parameter to set an locality name in any randomly generated SSL certificate, default 'New York', Optional>",
"organizationName": "<parameter to set an organizational name in any randomly generated SSL certificate, default 'RTL', Optional>",
"organizationalUnit": "<parameter to set an organizational unit in any randomly generated SSL certificate, default 'RTL', Optional>",
"encryptionBits": <parameter to set default encryption strength for any randomly generated SSL certificate, default 2048, Optional>,
"validForYears": <parameter to set default validity duration (in years) for any randomly generated SSL certificate, default 10, Optional>,
"rejectUnauthorized": <parameter to set server-side SSL rejection if certificates are not valid per the ca file, default false, Optional>,
"requestCert": <parameter to require client x509 certificates to communicate with RTL. Can be used to block users without PKI certificates issued by an admin with a valid ca, default false, Optional>
},
"SSO": {
"rtlSSO": <parameter to turn SSO off/on. Allowed values - 1 (single sign on via an external cookie), 0 (stand alone RTL authentication), default 0, Required>,
"rtlCookiePath": "<Full path of the cookie file including the file name. The application url needs to pass the value from this cookie file as query param 'access-key' for the SSO authentication to work, Required if SSO=1 else empty (Optional)>",
Expand Down Expand Up @@ -50,6 +67,7 @@ If the environment variables are set, it will take precedence over the parameter
<br />
PORT (port number for the rtl node server, default 3000, Required)<br />
HOST (host for the rtl node server, default localhost, Optional)<br />
SSL (true, false, or a stringified JSON object formatted like the 'ssl' property above)<br />
APP_PASSWORD (Plaintext password to be provided by the parent container, NOT suggested for standalone RTL applications, to be used by Umbrel) (Optional)<br />
LN_IMPLEMENTATION (LND/CLN/ECL. Default 'LND', Required)<br />
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://localhost:8080) (Required)<br />
Expand Down
2 changes: 1 addition & 1 deletion .github/docs/RTL_SSL_setup.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
### Setup https access for RTL
### Setup https forwarding for RTL

Forward the ports 80 and 3002 on the router to the device running RTL.
Allow the ports through the firewall of the device.
Expand Down
1 change: 1 addition & 0 deletions Sample-RTL-Config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"ssl": false,
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
Expand Down
14 changes: 11 additions & 3 deletions backend/controllers/cln/balance.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export const getBalance = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/getBalance';
request(options).then((body) => {
request(options)
.then((body) => {
if (!body.totalBalance) {
body.totalBalance = 0;
}
Expand All @@ -21,9 +22,16 @@ export const getBalance = (req, res, next) => {
if (!body.unconfBalance) {
body.unconfBalance = 0;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Balance', msg: 'Balance Received', data: body });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Balance',
msg: 'Balance Received',
data: body
});
res.status(200).json(body);
}).catch((errRes) => {
})
.catch((errRes) => {
const err = common.handleError(errRes, 'Balance', 'Get Balance Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
Expand Down
152 changes: 123 additions & 29 deletions backend/controllers/cln/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,39 @@ let options = null;
const logger = Logger;
const common = Common;
export const listChannels = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Getting Channels..'
});
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listChannels';
request(options).then((body) => {
request(options)
.then((body) => {
body.map((channel) => {
if (!channel.alias || channel.alias === '') {
channel.alias = channel.id.substring(0, 20);
}
const local = (channel.msatoshi_to_us) ? channel.msatoshi_to_us : 0;
const remote = (channel.msatoshi_to_them) ? channel.msatoshi_to_them : 0;
const local = channel.msatoshi_to_us ? channel.msatoshi_to_us : 0;
const remote = channel.msatoshi_to_them ? channel.msatoshi_to_them : 0;
const total = channel.msatoshi_total ? channel.msatoshi_total : 0;
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
channel.balancedness = total === 0 ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
return channel;
});
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channels List Received', data: body });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Channels List Received',
data: body
});
res.status(200).json(body);
}).catch((errRes) => {
})
.catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'List Channels Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
Expand All @@ -37,28 +50,63 @@ export const openChannel = (req, res, next) => {
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/openChannel';
options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Options', data: options.body });
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Opened', data: body });
logger.log({
selectedNode: req.session.selectedNode,
level: 'DEBUG',
fileName: 'Channels',
msg: 'Open Channel Options',
data: options.body
});
request
.post(options)
.then((body) => {
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Channel Opened',
data: body
});
res.status(201).json(body);
}).catch((errRes) => {
})
.catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Open Channel Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const setChannelFee = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Setting Channel Fee..' });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Setting Channel Fee..'
});
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/setChannelFee';
options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.body });
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Updated Channel Policy', data: body });
logger.log({
selectedNode: req.session.selectedNode,
level: 'DEBUG',
fileName: 'Channels',
msg: 'Update Channel Policy Options',
data: options.body
});
request
.post(options)
.then((body) => {
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Updated Channel Policy',
data: body
});
res.status(201).json(body);
}).catch((errRes) => {
})
.catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Update Channel Policy Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
Expand All @@ -71,51 +119,97 @@ export const closeChannel = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const unilateralTimeoutQuery = req.query.force ? '?unilateralTimeout=1' : '';
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/closeChannel/' + req.params.channelId + unilateralTimeoutQuery;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Closing Channel', data: options.url });
request.delete(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Closed', data: body });
options.url =
req.session.selectedNode.ln_server_url +
'/v1/channel/closeChannel/' +
req.params.channelId +
unilateralTimeoutQuery;
logger.log({
selectedNode: req.session.selectedNode,
level: 'DEBUG',
fileName: 'Channels',
msg: 'Closing Channel',
data: options.url
});
request
.delete(options)
.then((body) => {
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Channel Closed',
data: body
});
res.status(204).json(body);
}).catch((errRes) => {
})
.catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Close Channel Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const getLocalRemoteBalance = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Local & Remote Balances..' });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Getting Local & Remote Balances..'
});
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/localremotebal';
request(options).then((body) => {
request(options)
.then((body) => {
if (!body.localBalance) {
body.localBalance = 0;
}
if (!body.remoteBalance) {
body.remoteBalance = 0;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Local Remote Balance Received', data: body });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Local Remote Balance Received',
data: body
});
res.status(200).json(body);
}).catch((errRes) => {
})
.catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Local Remote Balance Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const listForwards = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channel List Forwards..' });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Getting Channel List Forwards..'
});
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listForwards?status=' + req.query.status;
request.get(options).then((body) => {
request
.get(options)
.then((body) => {
if (body && body.length > 0) {
body = common.sortDescByKey(body, 'received_time');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Forwarding History Received For Status' + req.query.status, data: body });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Channels',
msg: 'Forwarding History Received For Status' + req.query.status,
data: body
});
res.status(200).json(body);
}).catch((errRes) => {
})
.catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Forwarding History Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
Expand Down
14 changes: 11 additions & 3 deletions backend/controllers/cln/fees.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ export const getFees = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/getFees';
request(options).then((body) => {
request(options)
.then((body) => {
if (!body.feeCollected) {
body.feeCollected = 0;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: body });
logger.log({
selectedNode: req.session.selectedNode,
level: 'INFO',
fileName: 'Fees',
msg: 'Fee Received',
data: body
});
res.status(200).json(body);
}).catch((errRes) => {
})
.catch((errRes) => {
const err = common.handleError(errRes, 'Fees', 'Get Fees Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
Expand Down
Loading