Wrong TLS with RESTDataSource from Apollo and HttpsProxyAgent - node.js

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.

Related

Allow Legacy Renegotiation for NodeJs

The best way to solve this would be to update the SSL endpoint I'm trying to connect to but I don't have the ability too.
I'm trying to reach a SOAP endpoint (it's painful) for an application that is barily being maintained and thus probably won't be able to get the proper SSL patch.
It's sitting behind a proxy that is doing active SSL rewrites and could also be to blame for the error:
var request = require("request")
var soap = require("soap")
const fs = require('fs')
var specialRequest = request.defaults({
ca: fs.readFileSync("rewrite-example.pem")
})
var options = { request: specialRequest }
const WSDL = "https://SSL-rewrite.example?wsdl"
soap.createClient(WSDL, options, function(err, client) {
if(err) throw Error(err)
})
Error:
Uncaught TypeError: req.then is not a function
at HttpClient.request (../node_modules/soap/lib/http.js:191:13)
at Object.open_wsdl (../node_modules/soap/lib/wsdl/index.js:1271:20)
at openWsdl (../node_modules/soap/lib/soap.js:70:16)
at ../node_modules/soap/lib/soap.js:48:13
at _requestWSDL (../node_modules/soap/lib/soap.js:76:9)
at Object.createClient (../node_modules/soap/lib/soap.js:94:5)
> Uncaught: Error: write EPROTO C017726B8C7F0000:error:0A000152:SSL routines:final_renegotiate:unsafe legacy renegotiation disabled:../deps/openssl/openssl/ssl/statem/extensions.c:908
From what I found here, it's possible to create a custom OpenSSL config file allowing unsafe legacy renegotiation. And using Node's --openssl-config flag, it should be possible to "ignore" the renegotiation. I've tried writing a custom config file as written in the first link and passing it in but with no avail.
This question has been asked before, though reverting to an older verision of Node would not be ideal.
What might be some other wasys to resolve this?
As you already have found this error is coming from CVE-2009-3555, this is IIS issue, so it even won't be ignored using node flags. Since node 17 or 18 they removed OpenSSL option to accept legacy servers.
I thing better solution in your case is passing httpsAgent with option.
soap.js uses Axios as of v0.40.0 according to readme, so you should set request param like this:
const crypto = require('crypto')
const options = {
request: axios.create({
// axios options
httpsAgent: new https.Agent({
// for self signed you could also add
// rejectUnauthorized: false,
// allow legacy server
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
}),
}),
}
https.Agent's secureOptions is a numeric bitmask of the SSL_OP_* options.

Nestjs wss handleConnection(socket) socket.handshake is undefined and cannot access headers to authenticate

I have a Nestjs gateway where I'm trying to run authorization logic depending on values from the headers but wherever I'm trying to access the handshake it always returns 'undefined'
I'm also trying this over SSL which might be making a difference.
main.ts:
import { WsAdapter } from "#nestjs/platform-ws";
app = await NestFactory.create(UserMicroserviceModule, {
httpsOptions: {
key: get_ssl("key"),
cert: get_ssl("cert"),
},
});
app.useWebSocketAdapter(new WsAdapter(app));
await app.listen(process.env.PORT || 443);
gateway.ts:
#UseGuards(SessionGuard)
#WebSocketGateway({ path: "/user" })
export class UserMicroserviceGateway implements OnGatewayConnection {
handleConnection(socket) {
socket.handshake // <== undefined
}
session.guard.ts:
const socket_cookie: any = context.switchToWs().getClient().handshake; // <== undefined
And also, although I've addeded the session guard on the whole gateway --- the guard does not trigger for handleConnection()
Guards and other decorators don't work for the handleConnection() method in nestjs currently and the handshake isn't a concept that exists on the vanilla socket but more of a socket.io thing, so I just switched to using that and manually ran the actions needed for verifications inside the handleConnection() method.

Nodejs Fetch API: unable to verify the first certificate

I'm using the fetch API module in my Philips Hue project and when I make a call to the local ip address (my hub) it produces that error in the title.
const fetch = require('node-fetch');
const gateway = "192.168.0.12";
const username = "username";
let getLights = function(){
fetch(`https://${gateway}/api/${username}/lights`, {
method: 'GET'
}).then((res) => {
return res.json();
}).then((json) => {
console.log(json);
});
}
module.exports = {getLights};
Any SECURE fix this will eventually go onto the public internet for me to access my lights from anywhere sooo?
To skip the SSL tests, you can use this:
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;
It seems like you tried to access it using HTTPS. Most likely on your local network it is going to be HTTP
So by changing https://${gateway}/api/${username}/lights to http://${gateway}/api/${username}/lights should work.
If you're trying to keep it HTTPS then you will have to install a SSL certificate authority onto your network.
These may be useful sources if you're trying to get that done:
https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/
https://letsencrypt.org/docs/certificates-for-localhost/

NodeJS EventSource with https proxy doesn't work

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

How to use PM2 Cluster with Socket IO?

I am developing an application that relies completely on Socket.io. As we all know NodeJS by default runs only on one core. Now I would like to scale it across multiple cores. I am finding it difficult to make socketio work with PM2 Cluster Mode. Any sample code would help.
I am using Artillery to test. And when the app runs on single core I get the response while It runs in cluster the response would be NaN
When Ran Without Cluster
PM2 docs say
Be sure your application is stateless meaning that no local data is
stored in the process, for example sessions/websocket connections,
session-memory and related. Use Redis, Mongo or other databases to
share states between processes.
Socket.io is not stateless.
Kubernetes implementation get around the statefull issues by routing based on source IP to a specific instance. This is still not 100% since some sources may present more than one IP address. I know this is not PM2, but gives you an idea of the complexity.
NESTjs SERVER
I use Socket server 2.4.1 so then i get the compatible redis adapter that is 5.4.0
I need to extend nest's adepter class "ioAdapter" that class only works for normal ws connections not our pm2 clusters
import { IoAdapter } from '#nestjs/platform-socket.io';
import * as redisIOAdapter from 'socket.io-redis';
import { config } from './config';
export class RedisIoAdapter extends IoAdapter {
createIOServer(port: number, options?: any): any {
const server = super.createIOServer(port, options);
const redisAdapter = redisIOAdapter({
host: config.server.redisUrl,
port: config.server.redisPort,
});
server.adapter(redisAdapter);
return server;
}
}
That is actually nestjs implementation
Now i need to tell nest im using that implementetion so i go to main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { config } from './config';
import { RedisIoAdapter } from './socket-io.adapter';
import { EventEmitter } from 'events';
async function bootstrap() {
EventEmitter.defaultMaxListeners = 15;
const app = await NestFactory.create(AppModule);
app.enableCors();
app.useWebSocketAdapter(new RedisIoAdapter(app));
await app.listen(config.server.port);
}
bootstrap();
I have a lot of events for this one so i had to up my max event count
now for every gateway you got, you need to use a different connection strategy, so instead of using polling you need to go to websocket directly
...
#WebSocketGateway({ transports: ['websocket'] })
export class AppGateway implements OnGatewayConnection, OnGatewayDisconnect {
...
or if you are using namespaces
...
#WebSocketGateway({ transports: ['websocket'], namespace: 'user' })
export class UsersGateway {
...
last step is to install the redis database on your AWS instance and that is another thing; and also install pm2
nest build
pm2 i -g pm2
pm2 start dist/main.js -i 4
CLIENT
const config: SocketIoConfig = {
url: environment.server.admin_url, //http:localhost:3000
options: {
transports: ['websocket'],
},
};
You can now test your websocket server using FireCamp
Try using this lib:
https://github.com/luoyjx/socket.io-redis-stateless
It makes socket io stateless through redis.
You need to setup Redis with your Node server. Here is how I managed to get cluster mode to work with Socket.io
First install Redis. If you are using Ubuntu, follow this link: https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-18-04
Then:
npm i socket.io-redis
Now place Redis in your Node server
const redisAdapter = require('socket.io-redis')
global.io = require('socket.io')(server, { transports: [ 'websocket' ]})
io.adapter(redisAdapter({ host: 'localhost', port: 6379 }))
That was all I had to do to get PM2 cluster mode to work with socket.io in my server.

Resources