NodeJS EventSource with https proxy doesn't work - node.js

We have a client which connects to the server. The server can be accessed both: http and https.
Https server contains CA certified SSL key.
Since the client is running behind a corporate proxy, we used the following command:
const events = this.proxy ? new EventSource(this.source, { proxy: this.proxy }) : new EventSource(this.source)
So, the current command works only when connecting to http://server.
If we try to connect to the https://server, the following error received:
Event { type: 'error', status: 400, message: 'Bad Request' }
So we tried to set:
const events = this.proxy ? new EventSource(this.source, {https: {proxy: this.proxy, rejectUnauthorized: false} } ) : new EventSource(this.source)
or
const events = this.proxy ? new EventSource(this.source, {https: {proxy: this.proxy} } ) : new EventSource(this.source)
In both cases, we got a TIME OUT error.
Is there something we are missing? How should we set https connection behind proxy?

The solution is to use 'global-agent' or 'global-tunnel-ng' packages:
// Support working behind a corporate proxy
const MAJOR_NODEJS_VERSION = parseInt(process.version.slice(1).split('.')[0], 10);
if (MAJOR_NODEJS_VERSION >= 10) {
// `global-agent` works with Node.js v10 and above. Proxy env should be defined "export GLOBAL_AGENT_HTTP_PROXY=http://127.0.0.1:8080"
require('global-agent').bootstrap()
} else {
// `global-tunnel-ng` works only with Node.js v10 and below. Uses npm proxy settings
require('global-tunnel-ng').initialize()
}
const events = new EventSource(this.source)
Read here for more info: https://www.npmjs.com/package/global-agent

Related

NodeJs - Secure Web Socket and Client Connection

I need to convert an application with websocket in a secure-websocket. (under windows)
Im using nodeJs as websocket server and a simple html page to connect to it.
Searching on google and here, I found this approach:
Create a certificate and a key for server. I've followed this tutorial:
https://www.cloudinsidr.com/content/how-to-install-the-most-recent-version-of-openssl-on-windows-10-in-64-bit/
After creating a .key and a .pem, I'have modified my nodejs websocket server to introduce the certificate:
const httpsOptions = {
key: fs.readFileSync('./api/security/cert.key'),
cert: fs.readFileSync('./api/security/cert.pem')
}
this._http = require('http');
this._server = this._http.createServer(httpsOptions , function(req, res) { this.closeCurrentConnections(req,res)}.bind(this));
var serverConfig = {
server: this._server,
autoAcceptConnections: false
}
this._wsServer = new WebSocketServer(serverConfig);
The Websocket server seems up when I start the nodejs
Now, in the client page I had this code:
var websocket_server = "ws://localhost:8128";
var echo_service = new WebSocket(websocket_server,"echo-protocol");
[...]
I changed it with the following code calling this page over HTTPS instead of simple HTTP:
var websocket_server = "wss://localhost:8128";
var echo_service = new WebSocket(websocket_server,"echo-protocol");
[...]
I got an error on client page:
testing_page.html:283 WebSocket connection to 'wss://localhost:8128/' failed: Error in connection establishment: net::ERR_SSL_PROTOCOL_ERROR
I think is due to missing certificate.
I've tried to import my previous create certificate on chrome but I cant import because chrome is especting a .crt and/or other format. I've tried to force .pem but it doens't work.
What Im missing?

Wrong TLS with RESTDataSource from Apollo and HttpsProxyAgent

I'm setting up a link between my Apollo Server (Node) and a REST API. My endpoint is https://app.myproject.local/api/v1 and is served via Hotel through a Pacfile available from http://localhost:2000/proxy.pac.
In reality this endpoint is also available from http://localhost:4000/api/v1 but I want to access it with Hotel.
I figured app.myproject.local wasn't resolved if I tried to access it directly in the node application, so I should go through HttpsProxyAgent and get it from there.
import { RESTDataSource } from 'apollo-datasource-rest'
import HttpsProxyAgent from 'https-proxy-agent'
import { restConfig } from '../config/restConfig'
export class RestAPI extends RESTDataSource {
constructor() {
super()
this.baseURL = restConfig.endpoint
}
public willSendRequest(request: any) {
request.agent = new HttpsProxyAgent({
host: 'localhost',
port: 2000,
secureProxy: false,
rejectUnauthorized: false,
})
}
public async test() {
return this.get('/status')
}
}
Despite having rejectUnauthorized it throws an error
(node:40593) UnhandledPromiseRejectionWarning: FetchError: request to https://app.myproject.local/api/v1/organizations/current failed, reason: write EPROTO 4474312128:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:332:
When I try to do the same kind of fetch with cURL it does work with
curl --insecure --proxy http://localhost:2000/proxy.pac https://app.myproject.local/api/v1/status
It may be a misunderstanding from my part but I thought rejectUnauthorized would bypass this SSL certificate problem. I'm actually using this in development environment so it does not matter so much, in production I won't need to go through all this.
I'm using the documentation of https://node.readthedocs.io/en/latest/api/tls/#tlsconnectport-host-options-callback to help me pass arguments to HttpsProxyAgent
const https = require('https')
...
willSendRequest(request) {
request.agent = new https.Agent({ rejectUnauthorized: false })
}
...
proxy.pac is a file that tells a web browser which proxy to use for a given request. You generally don't use it as a proxy itself.

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.

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.

Riak connectivity from Node

this is probably not a bug but rather a gap in my understanding but putting it here as afraid havent been able to find a way so far. Appreciate if you can provide your inputs please.
I'm trying to connect to my Riak cluster (hosted on AWS) of 3 nodes via two options - 1) Using an ejabberd server, and 2) using a Node server.
Connecting from the ejabberd server is successful after I put the hostname and port in the ejabberd configuration, but when I use a simple Node server (code below), I get the "Error: No RiakNodes available to execute command." error. Am I missing out on something here please - I can confirm that the 3 nodes are indeed up with Riak running? Note that if I dont do the client ping on the nodes, the server doesnt throw any error, so it is probably got to do with how pings are handled. The same server (without the ping) gives an ECONNREFUSED error if one of the nodes are brought down. So clearly the connection is going through but not the ping.
Apologize if am missing out on something basic here ... even the firewall settings for the Riak nodes have been set to all inbound, so it is not a case of the ejabberd server having access but not the Node server.
var async = require('async');
var assert = require('assert');
var logger = require('winston');
var Riak = require('basho-riak-client');
logger.remove(logger.transports.Console);
logger.add(logger.transports.Console, {
level : 'debug',
colorize : true,
timestamp : true
});
var nodes = [
'ip-xx-xx-xx-xx:8087',
'ip-xx-xx-xx-xx:8087',
'ip-xx-xx-xx-xx:8087'
];
var client = new Riak.Client(nodes, function (err, c) {
logger.info('Now inside Riak.Client');
// NB: at this point the client is fully initialized, and
// 'client' and 'c' are the same object
});
client.ping(function (err, rslt) {
logger.info('Now entered client.ping');
if (err) {
logger.info('There is an error encountered in client.ping');
throw new Error(err);
} else {
// On success, ping returns true
logger.info('client.ping has resulted in success!');
assert(rslt === true);
}
});

Resources