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 #1027

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
80 changes: 67 additions & 13 deletions backend/utils/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import CORS from './cors.js';
import CSRF from './csrf.js';
import CertificateIdentity from './certificateIdentity.js';
import sharedRoutes from '../routes/shared/index.js';
import lndRoutes from '../routes/lnd/index.js';
import clnRoutes from '../routes/cln/index.js';
Expand All @@ -31,53 +32,106 @@ export class ExpressApplication {
this.loadConfiguration = () => {
this.config.setServerConfiguration();
};
this.setCORS = () => { CORS.mount(this.app); };
this.setCSRF = () => { CSRF.mount(this.app); };
this.setCORS = () => {
CORS.mount(this.app);
};
this.setCSRF = () => {
CSRF.mount(this.app);
};
this.setApplicationRoutes = () => {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Setting up Application Routes..' });
this.logger.log({
selectedNode: this.common.initSelectedNode,
level: 'INFO',
fileName: 'App',
msg: 'Setting up Application Routes..'
});
this.app.use(this.common.baseHref + '/api', sharedRoutes);
this.app.use(this.common.baseHref + '/api/lnd', lndRoutes);
this.app.use(this.common.baseHref + '/api/cln', clnRoutes);
this.app.use(this.common.baseHref + '/api/ecl', eclRoutes);
this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend')));
this.app.use((req, res, next) => {
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : '');
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : '', { secure: this.common.ssl ? true : false });
res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html'));
});
this.app.use((err, req, res, next) => this.handleApplicationErrors(err, res));
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' });
this.logger.log({
selectedNode: this.common.initSelectedNode,
level: 'INFO',
fileName: 'App',
msg: 'Application Routes Set'
});
};
this.handleApplicationErrors = (err, res) => {
switch (err.code) {
case 'EACCES':
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server requires elevated privileges' });
this.logger.log({
selectedNode: this.common.initSelectedNode,
level: 'ERROR',
fileName: 'App',
msg: 'Server requires elevated privileges'
});
res.status(406).send('Server requires elevated privileges.');
break;
case 'EADDRINUSE':
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is already in use' });
this.logger.log({
selectedNode: this.common.initSelectedNode,
level: 'ERROR',
fileName: 'App',
msg: 'Server is already in use'
});
res.status(409).send('Server is already in use.');
break;
case 'ECONNREFUSED':
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is down/locked' });
this.logger.log({
selectedNode: this.common.initSelectedNode,
level: 'ERROR',
fileName: 'App',
msg: 'Server is down/locked'
});
res.status(401).send('Server is down/locked.');
break;
case 'EBADCSRFTOKEN':
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Invalid CSRF token. Form tempered.' });
this.logger.log({
selectedNode: this.common.initSelectedNode,
level: 'ERROR',
fileName: 'App',
msg: 'Invalid CSRF token. Form tempered.'
});
res.status(403).send('Invalid CSRF token, form tempered.');
break;
default:
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'DEFUALT ERROR', error: err });
this.logger.log({
selectedNode: this.common.initSelectedNode,
level: 'ERROR',
fileName: 'App',
msg: 'DEFUALT ERROR',
error: err
});
res.status(400).send(JSON.stringify(err));
break;
}
};
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Starting Express Application..' });
this.logger.log({
selectedNode: this.common.initSelectedNode,
level: 'INFO',
fileName: 'App',
msg: 'Starting Express Application..'
});
this.loadConfiguration();
this.app.set('trust proxy', true);
this.app.use(sessions({ secret: this.common.secret_key, saveUninitialized: true, cookie: { secure: false, maxAge: ONE_DAY }, resave: false }));
if (this.common.ssl && this.common.ssl.requestCert) {
this.app.use(CertificateIdentity(this.common.ssl.rejectUnauthorized));
}
this.app.use(sessions({
secret: this.common.secret_key,
saveUninitialized: true,
cookie: { secure: this.common.ssl ? true : false, maxAge: ONE_DAY },
resave: false
}));
this.app.use(cookieParser(this.common.secret_key));
this.app.use(bodyParser.json({ limit: '25mb' }));
this.app.use(bodyParser.urlencoded({ extended: false, limit: '25mb' }));
this.loadConfiguration();
this.setCORS();
this.setCSRF();
this.setApplicationRoutes();
Expand Down
2 changes: 1 addition & 1 deletion backend/utils/authCheck.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Common } from './common.js';
import { Logger } from './logger.js';
const common = Common;
const logger = Logger;
const csurfProtection = csurf({ cookie: true });
const csurfProtection = csurf({ cookie: true, secure: Common.ssl ? true : false });
export const isAuthenticated = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
Expand Down
Loading