Access Node-RED from the Internet securely using NGROK
Much is mentioned in the Node-RED forum about exposing Node-RED to the Internet. This flow is an example that provides a reasonable amount of security for most casual use.
It makes use of a cloud service called NGROK and you will need to sign up for an account. NGROK is run by a commercial organisation and you have to put some level of trust in them that they aren't intercepting data though it seems unlikely. The service is widely used and would almost certainly be exposed if they were doing anything egregious.
The default configuration of ngrok isn't that secure so I did some playing and came up with a flow that turns the ngrok link on/off via a secure Telegram bot. That way, you only turn on the link when you actually need it.
You need to use a slightly more complex command than the default to get ngrok reasonably secure:
http https://localhost:1880 -bind-tls=true -auth='jk:jk' -inspect=false -region=eu
Obviously you should change the id/password pair and change the region if you don't want to use the EU. Also, if you don't have local Node-RED set up with https then you need to change the URL. This command sets up an HTTPS only connection regardless of whether you are using TLS locally. It also protects access to the link with an id/password.
You will be given an Internet facing random URL by NGROK that you then use in your browser. Unfortunately, the ngrok
command does not correctly output information as it starts which means that the only way to find out the URL remotely is to log into your ngrok account and view your dashboard. Alternatively, you can pay to get a reserved instance with a fixed URL.
An example flow is given that can both start ngrok and kill it:
I can't really share the Telegram part of the flow so easily since it uses my own bot ids, however:
When the /ngrok start
or /ngrok end
Telegram bot commands are received a function node is used:
const command = msg.payload.content
msg.payload.content = `NGROK is*${command}ing*`
msg.payload.options = { "parse_mode": "Markdown" }
msg.topic = 'TELEGRAM/<botname>/NGROK'
let msg2 = null
if ( command === " start" ) {
msg2 = {
topic: msg.topic,
payload: 'start'
}
}
let msg3 = null
if ( command === " end" ) {
msg3 = {
topic: msg.topic,
payload: 'end'
}
}
return [msg, msg2, msg3];
// EOF
Output 1 is just feedback, 2 and 3 are the important parts.
[{"id":"3407a95c.e462c6","type":"inject","z":"396e6d9.614e192","name":"start","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":2140,"wires":[["c24eb059.ab369"]]},{"id":"c24eb059.ab369","type":"exec","z":"396e6d9.614e192","command":"ngrok","addpay":false,"append":"http https://localhost:1880 -bind-tls=true -auth='jk:jk' -inspect=false -region=eu","useSpawn":"false","timer":"","oldrc":false,"name":"","x":410,"y":2140,"wires":[[],[],["364295d4.c3b10a"]]},{"id":"364295d4.c3b10a","type":"debug","z":"396e6d9.614e192","name":"NGROK Error Output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":620,"y":2140,"wires":[]},{"id":"c14ae7ba.7900a8","type":"status","z":"396e6d9.614e192","name":"","scope":["c24eb059.ab369"],"x":200,"y":2180,"wires":[["2bba47c7.fe0628"]]},{"id":"fe34ab54.9dce58","type":"debug","z":"396e6d9.614e192","name":"NGROK Status Output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":620,"y":2180,"wires":[]},{"id":"e8eea2dc.6ac12","type":"exec","z":"396e6d9.614e192","command":"kill","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":410,"y":2220,"wires":[["459d2525.b9a25c"],["459d2525.b9a25c"],["459d2525.b9a25c"]]},{"id":"48a318df.9d6098","type":"inject","z":"396e6d9.614e192","name":"","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":155,"y":2220,"wires":[["d4aebdad.d16aa"]],"l":false},{"id":"2bba47c7.fe0628","type":"change","z":"396e6d9.614e192","name":"","rules":[{"t":"set","p":"ngrokPid","pt":"flow","to":"$contains(status.text,\"pid:\") ? $number($replace(status.text,\"pid:\",\"\"))+1 : $null","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":2180,"wires":[["fe34ab54.9dce58"]]},{"id":"459d2525.b9a25c","type":"debug","z":"396e6d9.614e192","name":"NGROK Kill Output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":610,"y":2220,"wires":[]},{"id":"4b24c6b2.807768","type":"comment","z":"396e6d9.614e192","name":"Start/Kill NGROK","info":"## How this works\n\nYou can start NGROK manually or send anything you like to the `Start-NGROK` link. Similarly, you can end it with the end inject node or sending anything to the `End-NGROK` link.\n\nFor example, I have a Telegram bot set up to start/stop NGROK remotely so that I don't have to try and keep it running all the time.\n\nThe flow saves the PID of the running instance of NGROK. This pid is +1 above the pid that the exec node creates (which is the parent shell). The flow saves the correct pid in a flow variable & uses that to facilitate a kill command.\n\n## Why not the node-red-contrib-ngrok node?\n\nI don't think that creates a secure connection.\n\nThe default ngrok command starts *both* secure and insecure URL's to your system. \n\nIn addition, the node does not let you input a login as this method does. \n\nWithout a login, anyone who finds your URL will have full access to Node-RED.\n\n## Notes\n\n* Starting NGROK from the exec node creates **2** tasks.\n* To Kill NGROK this way, you have to kill the **second** task not the one that the exec node reports back.\n* You cannot use `spawn` mode with NGROK, it doesn't work correctly. While it starts a session, it doesn't let you interact with it correctly (e.g. you cannot successfully log in).\n* The default NGROK command **IS NOT SECURE** - use the format given here.\n* If you allow the NGROK client to start its inspect web interface, you can only interact with it locally on the device - http(s)://localhost:4040\n* Don't forget that the free version of NGROK will terminate the link after 6 hours. Using this flow, you will have to end and then start again. You could easily automate this with a node like cron-plus or bigtimer.\n\nCommand used:\n\n```bash\nngrok http https://localhost:1880 -bind-tls=true -auth=someid:somepassword -inspect=false -region=eu\n```\n\n* *`-bind-tls=true`* - turns OFF the http link and only serves the https one.\n* *`https://localhost:1880`* - change to http if you haven't configured Node-RED for TLS. That would still be reasonably secure as long as you always use the NGROK URL since you are forcing https externally.\n* *`region=eu`* - you can an appropriate NGROK region with this, EU in this case.\n* *`-inspect=false`* - turns off the inspector web interface. You don't need this anyway if accessing remotely as you can only reach it locally.","x":400,"y":2100,"wires":[]},{"id":"d4aebdad.d16aa","type":"change","z":"396e6d9.614e192","name":"flow.ngrokPid","rules":[{"t":"set","p":"payload","pt":"msg","to":"ngrokPid","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":2220,"wires":[["e8eea2dc.6ac12"]]},{"id":"d347b34c.b62f8","type":"link in","z":"396e6d9.614e192","name":"Start-NGROK","links":["39de9645.73912a"],"x":215,"y":2100,"wires":[["c24eb059.ab369"]]},{"id":"15e9ee7c.9a6722","type":"link in","z":"396e6d9.614e192","name":"End-NGROK","links":["1d19c614.25eb6a"],"x":155,"y":2260,"wires":[["d4aebdad.d16aa"]]}]