balena tunnel connections are not encrypted

I have been diving a bit into how the balena-cli commands work and I’ve noticed a security hole. A tunnel is established by using vpn.balena-cloud.com as an HTTP proxy server using CONNECT. However, the connection from the client host to vpn.balena-cloud.com is plain TCP, it is not encrypted via SSL. The authorization scheme is HTTP BASIC, using the Balena user and the Balena token as a password. This exposes the client to a couple of security problems:

  • First of all, anyone with access can sniff the connection and get a user’s user ID and Balena token, exposing all the user’s resources while the token is still valid
  • Second, any traffic that is then sent through the tunnel is exposed (again, to sniffing)

Are there any plans to secure access? Either by using SSL, or by using the SSH proxy to establish the tunnel instead of an HTTP proxy.

Hey, thanks for reaching out. If this is the case, it sounds like it should definitely be patched. I’ve asked input from the team and we’ll let you know.

Here’s an actually secure version of balena tunnel running from the shell, piggybacking on the Balena SSH server:

socat tcp-listen:<localPort>,reuseaddr,fork "system:ssh <balena_user>@ssh.balena-devices.com host <deviceUuid> nc localhost <remotePort>"

@mario, many thanks for finding and sharing this security gap. It is indeed something that we have now confirmed, and were previous unaware of. We are now discussing which of the two solutions you’ve suggested should be implemented: enabling TLS on the existing TCP connection socket (which would require changes to the backend too), or using the ssh.balena-devices.com proxy plus /usr/bin/nc on balenaOS. We will prioritise this issue, and post updates on this thread.

One comment regarding using nc. That would also enable developers and administrators to tunnel to non-exposed container ports. I’ve implemented this hack in a script (which is useful for our use-case):

  remote_cmd="ip=\"\$(balena inspect \$(balena network ls --format '{{.Name}}' | grep default)  | jq -r '.[].Containers[]|select(.Name|startswith(\"${container}\"))|.IPv4Address|gsub(\"/[0-9]*$\"; \"\")')\"; nc \$ip '${remote_port}'"

  # Not too happy about UserKnownHostsFile=/dev/null and StrictHostKeyChecking=false, but there is no (easy) way
  # to ask the user to add ssh.balena-devices.com to their known_hosts file (plus, this is what balena ssh does)
  socat tcp-listen:${local_port},reuseaddr,fork "exec:ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ${user}@ssh.balena-devices.com host ${device_uuid} ${remote_cmd}"

Thank you for the additional suggestion. :+1:
I’ve created a couple of GitHub issues for added visibility and progress tracking:

As of today, balenaCloud has a new, TLS-encrypted backend endpoint for use by the balena tunnel CLI command:

  • New: tunnel.balena-cloud.com:443 (TLS enabled)
  • Old: vpn.balena-cloud.com:3128

balena CLI version 12.38.5 or later makes use of the new endpoint. Access to the old endpoint will soon be closed. Therefore, balenaCloud users are advised to upgrade their CLI installations in order to continue using the balena tunnel command without disruption.

openBalena has also introduced the new tunnel endpoint starting with openBalena v3.1.2. openBalena users should match the CLI release to the openBalena release:

  • openBalena v3.1.2 or later: use CLI v12.38.5 or later
  • older openBalena releases: use older CLI releases

Thank you again @mario for reporting this issue and sharing the workarounds! :100: