Secure websocket server from NodeJS app

Hi

I want to create a secure websocket connection from my frontend hosted on Google Firebase Hosting to my Balena device to be able to configure different settings on the device. I am perfectly able to connect from localhost without SSL like this:

        this.server = http.createServer()

        this.socket = new WebSocket.server({
            httpServer: this.server,
            autoAcceptConnections: false
        })

        this.server.listen(80)

And connecting from the frontend like this:

        const address = `ws://192.168.1.49:80`
        const ws = new websocket(address)

If I parse a request handler to the NodeJS server instance on the device, I am able to get a response by accessing the public URL of the device through HTTPS, but i am not able the connect a websocket to the device through WSS from the frontend hosted on Google through HTTPS. I have also tried connecting the websocket with the public URL without luck:

       const address = `wss://<DEVICE_ID>.balena-devices.com:80`

Obviously it is not that simple and I recognize I might need to create a HTTPS server on the device, however i have had some problems generating the right certificates. Another possibility might be to utilize the certificates already used for the public HTTPS URL of the device. I have not been able find any good examples or references showing how to access these certificates or creating a secure websocket connecting to the device.

Hi @TobiasEmil,

If I’m correct, you’re trying to connect from your frontend hosted on Google Firebase (client) to your Balena device (server) via a websocket, correct?

I don’t know what you’re trying to achieve, but I don’t think this is the correct approach. It’s better to have server host the websocket and let the frontend and Balena devices listen to this server. This way, you only have to maintain the SSL certificates for the server hosting the websocket instead of individual Balena devices. And the frontend can emit ‘commands’ to the server and the server can forward these ‘commands’ to the specific Balena device or to all devices.

But, like I said, I don’t know if this is what you want and what you’re trying to achieve?

Thanks for answering!

Yes, that is correctly understood. I want I that way around as the Balena device is acting as a gateway for a number of sensors in a home and the residents should be able to connect to the gateway both from a smartphone app and a web app. But I am open for changes to the architecture if it makes sense.

But would it not be possible just to utilize the existing certificate the Balena device uses to secure the public URL, or am i miss understanding something?

Hi @tobiasemil, I don’t think it will be possible to use the same cert that is used to secure the public URL, because as far as I understand, the SSL termination for the webURL is done on the balenaCloud backend and then the backend to device section is tunnelled through the balena VPN. So i think @vedicium suggestion is probably a good one. That being said, we have some upcoming features to allow one to do SSL termination of tunnels on the device itself, but that is not yet released.

Thanks for answering!

Alright. Do you know anything about the time schedule of the release of these features?

Hello,

Many apologies for the delay in response, unfortunately this ticket got slightly lost. We currently have the functionality out as a PR in review, so hopefully it shouldn’t be too much longer, but I can’t give you a solid ETA. If we’ve not checked back in with you in a couple of weeks, please feel free to contact us again and we’ll try and give you a more solid timescale.

Best regards, Heds

Hello,

Sorry to hijack this thread, but the title perfectly applies to a similar problem I’m having right now.

I want to use the public device url to allow some remote configuration tasks on a raspberry pi that locally connects to a bunch of objects via websockets on the local wifi, and serves a configuration page for all of them which relies on websockets too.

I’m using some code similar to the first snippet in @TobiasEmil’s first post (based on the wss library). I was able to access the configuration page from the public url by starting my express server on the port 80, but the sockets were not working because they were not secure (according to the js console). So I changed the port to 443 and now I cannot even get the configuration page to show up (I get an “upgrade required” message instead).

I understood from the replies in this thread that I should set the server’s port back to 80 because the public url is provided through a secured mechanism, but I don’t know how to get the websocket server to work. Is what I’m trying to achieve even possible ?

@joseph, welcome to the balena forums :slight_smile:
The SSL termination endpoint for the public device URL is in balenaCloud rather than on the device itself. The network traffic is initially decrypted on balenaCloud, then forwarded to the device over the VPN link (for which it is encrypted and decrypted once again) and finally delivered unencrypted to the websockets sever listening on port 80 on the device.

but the sockets were not working because they were not secure (according to the js console)

In the sentence above, did you mean “sockets” between your laptop and the raspberry pi, or sockets between the raspberry pi and the “bunch of objects on the local wifi”?

If you meant between your laptop and the Pi, check that the URL looked like the following:

wss://<long_uuid>.balena-devices.com/

Note that the URL above should not include a port number (especially not port 80). The client (web browser) will encrypt the traffic and send to port 443 of balenaCloud, but that same traffic will then reach your websockets server on port 80, decrypted.

Hi @pdcastro, thank you for your quick answer.

Yes I meant between the laptop and the pi, and I was talking about the browser’s console.
By checking your suggestions I just realized I was using the following code to instantiate my client side socket :

var ws = new WebSocket('ws://' + window.location.host);

I’ll try to use a wss:// prefix instead and I guess it will work.
Will get back here soon to let you know, sorry for the noise.

I just updated my code, now everything works as expected from the local network (I’m using a fixed IP for the pi) AND from the public url.

I think what was confusing me is that everything was already working as long as I was accessing the page from the local network.

The websocket instantiation code on the client page is now :

var protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
var ws = new WebSocket(protocol + window.location.host);

Thanks for the help !

That’s nice solution to make it work both locally and with public url. Glad you made it work!