Welcome to the blog of

Josh Deprez

Permalink link Published 10 May 2020

Complexifying the code editing experience

Until now I’ve looked upon containers (Docker, etc) with a certain small amount of bemusement. Sure, they are useful at work. I just never really saw a point for them at home, and didn’t find the time to even begin playing around.

But! I’ve finally encountered a problem to which Docker seems like a solution. Based on some tinkering this weekend, I can tentatively declare Docker to be a great partial solution to the problem of running unsigned code on macOS Catalina.

“Uh, use a virtual machine for this?” Well, yes, actually that seems to be what is happening under the hood with Docker on Mac. The nice part is then being able to expose small parts of the Mac’s filesystem to specific containers, and network containers to one another with user-defined bridges, as opposed to making them go via a physical interface.

Here’s a silly example that I’m totally going to use for reals.

Suppose I wanted to use code-server running on a nice beefy machine sitting at home, in a secure way, from an iPad, somewhere on the internet. I want to do this because VS Code seems nice, but isn’t on the App Store, and this is the closest I’ve seen (second to screen sharing the real thing). Let’s also suppose I don’t want to use coder.com’s paid service or Visual Studio Codespaces or GitHub Codespaces, for no particular reason.

In my mind the ideal setup is: I tap a home screen icon and I’m in without any passwords or fuss, but anyone else snooping around is denied a connection. The FAQ has some ideas, but long story short, instead of using code-server’s inbuilt authentication mechanisms (password?) I’ll go straight for mutual TLS authentication, and a separate reverse proxy is a reasonable solution for that part.

Another long story short, let’s use ghostunnel for the mutual TLS. It’s specifically built for the simple case of mutual TLS, and proxying allowed connections to some other server. Sadly I couldn’t get the binary release to run on Catalina directly. Maybe they’ll fix it, or I could build it from source, or … oh look there’s a container image on Docker Hub. There’s one for code-server too - in for a penny, in for a pound?

Recipe

Not included in this recipe:

  • port-forwarding 443 to the machine that will be running the containers,
  • pointing a domain name to the machine,
  • obtaining Let’s Encrypt certs for the domain,
  • installing and using github.com/square/certstrap to create a self-signed root CA plus the client certificates.

Notes on iPads, client TLS certificates, and WebSockets:

  • AirDrop-ing a .p12 file (created with a non-empty password) to the iPad worked for me. Don’t forget you still have to go into Settings > General > Profile to install it.
  • While you’re there, enable the NSURLSession WebSocket experimental feature (Settings > Safari > Advanced > Experimental Features > NSURLSession WebSocket), because of https://bugs.webkit.org/show_bug.cgi?id=158345.
Steps

Download the container images:

$ docker pull squareup/ghostunnel
$ docker pull codercom/code-server

Create a bridge network specifically for connecting the two containers together (and nothing else):

$ docker network create code-net

Note the two containers will be able to reference one another by name.

Put the fullchain.pem and privkey.pem for the domain, plus the .crt for the self-signed CA, into $HOME/code-server/certs.

Run the containers:

$ docker run \
  --name code-server \
  --detach \
  --network code-net \
  --volume "$HOME/code:/home/coder/" \
  -u "$(id -u):$(id -g)" \
  codercom/code-server:latest \
    --auth none \
    --disable-updates
$ docker run \
  --name code-proxy \
  --detach \
  --network code-net \
  --publish 443:443 \
  --volume "$HOME/code-server/certs:/home/ghostunnel/" \
  squareup/ghostunnel:latest server \
    --listen :443 \
    --target code-server:8080 \
    --unsafe-target \
    --cert "/home/ghostunnel/fullchain.pem" \
    --key "/home/ghostunnel/privkey.pem" \
    --cacert "/home/ghostunnel/Josh_Root_Authority.crt" \
    --allow-all

The code-server container has access to the files in $HOME/code, as the user who created the container, thanks to the --volume and -u flags. I disable code-server passwords with --auth none and disable auto-updates with --disable-updates, because these are mildly annoying.

The code-proxy container, running ghostunnel, has access to the certs via another --volume mount, and is exposed on port 443. ghostunnel is usually pointing at localhost, but in this case it is not, hence --unsafe-target.