SSL certificate on NginX - node.js

I have a DO droplet with nginX running 8 node apps as proxy servers.
For one of those i have a specific domain (e.g. 192.22.XX.20: 8888 -> mydomain.com) and I need HTTPS to get audio from users mic.
I have a PositiveSSL certificate defined on NginX but when i try to use it on that node app, nothing works. What Am I doing wrong?
nginx.default.conf
...
ssl on;
ssl_certificate /etc/nginx/ssl/.../ssl-bundle.crt;
ssl_certificate_key /etc/nginx/ssl/.../private.key;
ssl_prefer_server_ciphers on;
...
#proxy to a node app running on 8005 port
location /interpretame/ {
#return 301 $scheme://localhost:8005$request_uri;
proxy_pass http://localhost:8005/;
}
node app.js
```
...
var https_options = {
ca: fs.readFileSync("./cert/example_ca.crt"),
key: fs.readFileSync("./cert/example.key"),
cert: fs.readFileSync("./cert/example.crt")
};
...
https.createServer(https_options, app).listen(port, function(err) {
if (err) {
console.log(err);
return;
}
console.log('Listening over HTTPS at ' + port);
});
```

How do you define the 'nothing works'? :)
The application does not boot at all? If yes, it is quite possible that this happens if you start the application with node app.js. You have to start the application with sudo node app.js and by the way this is very bad practice.

Related

Do I change app.listen('port number') when I finally publish my website to a hostserver?

This is my first time publishing a website so excuse me if my question sounds dumb or easy. So I finally finished the frontend and backend of my website and I'm going to publish it to a hostname from namecheap.com. I'm using express and an abstract example of how my backend looks would be like this:
const express = require("express");
const app = express();
const path = require('path');
app.use(express.static("./public"));
app.listen(5000, () => {
"server is listening!!"
})
I realize that using app.listen(5000, () => {}) is due to listening to a port in the local host. So how do I change it to finally publish it and for my hostserver to recognize it.
use nginx or apache upstream ,
apache and nginx as ssl termination , more secure and if you add caching you could handle more request per second
nginx config :
server {
#listen 80 ;
#listen [::]:80 ;
listen 443 ssl ;
server_name namecheap.com;
ssl_certificate /etc/nginx/ssl/domain-crt.txt;
ssl_certificate_key /etc/nginx/ssl/domain-key.txt;
ssl_session_cache shared:SSL:1m; # holds approx 4000 sessions
ssl_session_timeout 1h; # 1 hour during which sessions can be re-used.
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_buffer_size 4k;
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://localhost:5000;
proxy_buffering on;
}
}
If you are gonna host the website you might wanna add a port variable and assign the following value to it.
const PORT = process.env.PORT || 5000;
When you are on localhost testing the website the port variable will use the
5000
value otherwise when hosting the hosting service provides its own port to run the server which is the
process.env.PORT
make sure "PORT" is all caps. Then for running the server-
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server Started at Port ${PORT}`)
});
That should do it!

Node: PORT 443 requires elevated privileges error

I'm using PM2 to start the application and I pass PORT=443 as a parameter while starting the app. However, it returns with an error saying "PORT 443 requires elevated privileges". Though I have generated the certificate and key using openssl and referenced in the code. Appreciate your support
#!/usr/bin/env node
var app = require('../app');
var debug = require('debug')('ls-templates-server:server');
var https = require('https');
var fs = require('fs');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
var options = {
key: fs.readFileSync('/home/admin/cert/server.key'),
cert: fs.readFileSync('/home/admin/cert/server.cert')
};
var httpsServer = https.createServer(options, app);
/* for https (production stage) */
httpsServer.listen(port, "0.0.0.0");
httpsServer.on('error', onError);
httpsServer.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = httpsServer.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
Option 1 .
Run PM2 as sudo
If it doesn't work, achieve it using authbind
sudo apt-get install authbind
sudo touch /etc/authbind/byport/443
sudo chown %user% /etc/authbind/byport/443
sudo chmod 755 /etc/authbind/byport/443
Edit ~/.bashrc file and add
+alias pm2='authbind --deep pm2'
at the end of the file, and run
source ~/.bashrc
Finally ensure that pm2 is updated with authbind:
authbind --deep pm2 update
Option 2
Use a different PORT and use Nginx to reverse proxy your application
Eg : change your PORT to 3000
In Nginx , create a server block which forwards the request to your application.
Server Block Eg :
server {
#listen [::]:80;
server_name your-domain.com
#root /var/www/example.com;
#index index.html;
client_max_body_size 20M;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:3000;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_connect_timeout 500000;
proxy_send_timeout 500000;
proxy_read_timeout 500000;
send_timeout 500000;
}
listen 443 ssl;
ssl_certificate /home/admin/cert/server.cert;
ssl_certificate_key /home/admin/cert/server.key;
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
It's best to use Nginx / Authbind method, It's good to run as little as possible as a privileged user, as you want to restrict the potential damage in case someone exploits your program. You don't want to run your Node code as root unless you absolutely have to.
refer
https://pm2.keymetrics.io/docs/usage/specifics/#listening-on-port-80-w-o-root
https://www.digitalocean.com/community/tutorials/how-to-use-pm2-to-setup-a-node-js-production-environment-on-an-ubuntu-vps

Issue SSL handshake with own https proxy on NodeJS

I try to work on my own HTTPS Proxy and I can't create my https server, during the connexion initialization, I have this error message "tlsClientError Error: 101057795:error:1407609B:SSL routines:SSL23_GET_CLIENT_HELLO:https proxy request:openssl\ssl\s23_srvr.c:400".
Below the node JS source code:
private runHttpsServer() {
const instance = this;
const cert = fs.readFileSync("./certificate/server.crt", "utf8");
const key = fs.readFileSync("./certificate/key.pem", "utf8");
const options : https.ServerOptions = {
cert: cert,
key: key
};
const httpsServer = https.createServer(options, (req, res)=>{
console.log("Request ...");
instance.handleRequest.call(instance, req, res);
});
httpsServer.on("tlsClientError", (err : Error, tlsSocket : TLSSocket)=>{
console.log("tlsClientError", err.stack);
});
httpsServer.listen(7001);
}
When I browse to "https://localhost:7001/" directly without my proxy, I have no SSL handshake error.
When I browse to other website throughout my proxy, I have this SSL Handshake error.
Someone have already encountered this issue ?
Some can help me ?
It looks like you have the wrong understanding of how proxying HTTPS works. It is not that the client will make a TLS connection to the proxy as you assume but instead the client will send a plain HTTP request with the method CONNECT to the proxy in order to make the proxy establish a tunnel to the target host. Then the client will upgrade this tunnel to TLS and thus get an end-to-end protection between client and final server.
On the wire it will look something like this (the exact message might slightly vary - see RFC 2817 for details):
Client ------------------------> Proxy
- create TCP connection
- send to proxy:
> CONNECT google.com:443 HTTP/1.0\r\n
> \r\n
Proxy -----------------------------> Server
- create TCP connection
Client <------------------------ Proxy
- send to client
< HTTP/1.0 200 Connection established\r\n
< \r\n
Client <------------------------(Proxy)-----------------------------> Server
use tunnel from client to server through proxy to
- make the end-to-end TLS handshake
- transfer the HTTP messages within the TLS connection
... SSL routines:SSL23_GET_CLIENT_HELLO:https proxy request: ...
This error message says essentially that your proxy expected the start of the TLS handshake (ClientHello) but instead got a https proxy request, i.e. CONNECT .... You need to fix your proxy to properly handle https proxy requests which work as I've described.
I also had the same problem. As mr. Steffen Ullrich answered we (me and you) have wrong understanding how proxying HTTPS work. Firstly youd need listen event CONNECTION in http then doing tls handshake. In code it look likes so:
http
.createServer((req, res) => requestHandler(req, res, false))
.on('connect', (req, cltSocket, head) => {
const srvUrl = url.parse(`http://${req.url}`);
const srvSocket = net.connect(
srvUrl.port,
srvUrl.hostname,
() => {
cltSocket.write('HTTP/1.1 200 Connection Established\r\n'
+ 'Proxy-agent: Node.js-Proxy\r\n'
+ '\r\n');
srvSocket.write(head);
srvSocket.pipe(cltSocket).on('error', (e) => console.log('srvSocket', e));
cltSocket.pipe(srvSocket).on('error', (e) => console.log('cltSocket', e));
},
);
})
.listen(9000, () => console.log('HTTP Server started listening on port 9000'));
you can learn about it in official site of Node.js
Full code of my https proxy is available here.

socket.io with nginx configuration

I am using Socket.IO on a Node server with Express and connection code of server side:
var http = require('http').Server(app);
var io = require('socket.io')(http);
Also client side connection like that:
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
var socket = io('https://example.com/example-service/');
And nginx conf is like that:
http {
upstream example-service {
server ip:8036;
}
server {
listen 6583;
#implementing WEB Socket
location / {
proxy_pass http://example-service/;
}
#my other application
location /test-app/ {
proxy_pass http://test-app/;
}
}
My socket connection code is in example-service and want to connect socket to other portal like test-app. If I given root path to socket service then its working proper. but if I passing nginx conf like that
# location /example-service/ {
# proxy_pass http://example-service/;
}
That time socket giving error 404.
Is there something I have to configure on client and server or Nginx?

Multiple SSL Certificates and HTTP/2 with Express.js

Scenario:
I have an express.js server which serves variations of the same static landing page based on where req.headers.host says the user is coming from - think sort of like A/B testing.
GET tulip.flower.com serves pages/flower.com/tulip.html
GET rose.flower.com serves pages/flower.com/rose.html
At the same time, this one IP is also responsible for:
GET potato.vegetable.com serving pages/vegetable.com/potato.html
It's important that these pages are served FAST, so they are precompiled and optimized in all sorts of ways.
The server now needs to:
Provide separate certificates for *.vegetables.com, *.fruits.com, *.rocks.net
Optionally provide no certificate for *.flowers.com
Offer HTTP2
The problem is that HTTP2 mandates a certificate, and there's now multiple certificates in play.
It appears that it's possible to use multiple certificates on one Node.js (and presumably by extension Express.js) server, but is it possible to combine it with a module like spdy, and if so, how?
Instead of hacking node, would it be smarter to pawn the task of sorting out http2 and SSL to nginx? Should the caching network like Imperva or Akamai handle this?
You can use also tls.createSecureContext, Nginx is not necassary.
MY example here:
const https = require("https");
const tls = require("tls");
const certs = {
"localhost": {
key: "./certs/localhost.key",
cert: "./certs/localhost.crt",
},
"example.com": {
key: "./certs/example.key",
cert: "./certs/example.cert",
ca: "./certs/example.ca",
},
}
function getSecureContexts(certs) {
if (!certs || Object.keys(certs).length === 0) {
throw new Error("Any certificate wasn't found.");
}
const certsToReturn = {};
for (const serverName of Object.keys(certs)) {
const appCert = certs[serverName];
certsToReturn[serverName] = tls.createSecureContext({
key: fs.readFileSync(appCert.key),
cert: fs.readFileSync(appCert.cert),
// If the 'ca' option is not given, then node.js will use the default
ca: appCert.ca ? sslCADecode(
fs.readFileSync(appCert.ca, "utf8"),
) : null,
});
}
return certsToReturn;
}
// if CA contains more certificates it will be parsed to array
function sslCADecode(source) {
if (!source || typeof (source) !== "string") {
return [];
}
return source.split(/-----END CERTIFICATE-----[\s\n]+-----BEGIN CERTIFICATE-----/)
.map((value, index: number, array) => {
if (index) {
value = "-----BEGIN CERTIFICATE-----" + value;
}
if (index !== array.length - 1) {
value = value + "-----END CERTIFICATE-----";
}
value = value.replace(/^\n+/, "").replace(/\n+$/, "");
return value;
});
}
const secureContexts = getSecureContexts(certs)
const options = {
// A function that will be called if the client supports SNI TLS extension.
SNICallback: (servername, cb) => {
const ctx = secureContexts[servername];
if (!ctx) {
log.debug(`Not found SSL certificate for host: ${servername}`);
} else {
log.debug(`SSL certificate has been found and assigned to ${servername}`);
}
if (cb) {
cb(null, ctx);
} else {
return ctx;
}
},
};
var https = require('https');
var httpsServer = https.createServer(options, (req, res) => { console.log(res, req)});
httpsServer.listen(443, function () {
console.log("Listening https on port: 443")
});
If you want test it:
edit /etc/hosts and add record 127.0.0.1 example.com
open browser with url https://example.com:443
Nginx can handle SSL termination nicely, and this will offload ssl processing power from your application servers.
If you have a secure private network between your nginx and application servers I recommend offloading ssl via nginx reverse proxy. In this practice nginx will listen on ssl, (certificates will be managed on nginx servers) then it will reverse proxy requests to application server on non ssl (so application servers dont require to have certificates on them, no ssl config and no ssl process burden).
If you don't have a secure private network between your nginx and application servers you can still use nginx as reverse proxy via configuring upstreams as ssl, but you will lose offloading benefits.
CDNs can do this too. They are basically reverse proxy + caching so I dont see a problem there.
Good read.
Let's Encrypt w/ Greenlock Express v3
I'm the author if Greenlock Express, which is Let's Encrypt for Node.js, Express, etc, and this use case is exactly what I made it for.
The basic setup looks like this:
require("greenlock-express")
.init(function getConfig() {
return {
package: require("./package.json")
manager: 'greenlock-manager-fs',
cluster: false,
configFile: '~/.config/greenlock/manager.json'
};
})
.serve(httpsWorker);
function httpsWorker(server) {
// Works with any Node app (Express, etc)
var app = require("./my-express-app.js");
// See, all normal stuff here
app.get("/hello", function(req, res) {
res.end("Hello, Encrypted World!");
});
// Serves on 80 and 443
// Get's SSL certificates magically!
server.serveApp(app);
}
It also works with node cluster so that you can take advantage of multiple cores.
It uses SNICallback to dynamically add certificates on the fly.
Site Management
The default manager plugin uses files on the file system, but there's great documentation on how to build your own.
Just to get started, the file-based plugin uses a config file that looks like this:
~/.config/greenlock/manager.json:
{
"subscriberEmail": "letsencrypt-test#therootcompany.com",
"agreeToTerms": true,
"sites": [
{
"subject": "example.com",
"altnames": ["example.com", "www.example.com"]
}
]
}
Very Extensible
I can't post all the possible options here, but it's very small and simple to start with, and very easy to scale out with advanced options as you need them.

Resources