Imran's Blog
Stuff I feel like blogging about.

The wonders of a proxy auto-config (PAC) file

Posted on

How I improved my work from home setup using a proxy auto-config (PAC) file.

My last couple of roles have had me using a MacBook Pro to develop on. I don't any qualms against mac os and much rather prefer it to a windows , though ideally I would like to use linux as it's what I use on my desktop computer.

As far as development goes I use the command line whenever I can, so my weapons of choice end up being (neo)vim and tmux. These work quite well on mac os (and are installable via homebrew). I have not tried to use these on windows but I do know its possible via the linux subsystem (at that point I'd rather be using linux).

When I work from home I attach the mac to a dongle that supplies it power and an Ethernet connection. I have the power settings set to never let the mac sleep, even with its lid closed. I can then connect to the mac with the following command from my desktop.

ssh  user@laptops-ip -t tmux -2 attach

This gives me my tmux session from the work mac on my regular desktop. This is convenient as I do not have a lot of space in my home for a separate work desk/setup.

I can do almost everything I need to via this setup (slack and related tools are all accessible via their web ui's) and over all it's been working pretty well. That is until my latest role.

This new role requires me to have a VPN connection active to access some internal sites (some of which are sorely needed during development). Up until now I had been developing as much as can using the ssh method mentioned above and then switching over to my work laptop to access the restricted internal sites. After a couple of weeks this grew pretty tiring. I would like a solution that would:

  1. Let me continue using my existing setup
  2. Not require me to install any work related software on my personal computer
  3. Proxy work related connections through my work computer and nothing else

# SOCKS5 Proxy Over SSH

ssh is quite a wonderful tool and comes with a solution built in for proxy-ing requests. Lets take a look at man ssh.

man ssh
-D [bind_address:]port
     Specifies a local “dynamic” application-level port forwarding.  This works by allocating a socket to listen to port on the local
     side, optionally bound to the specified bind_address.  Whenever a connection is made to this port, the connection is forwarded
     over the secure channel, and the application protocol is then used to determine where to connect to from the remote machine.
     Currently the SOCKS4 and SOCKS5 protocols are supported, and ssh will act as a SOCKS server.  Only root can forward privileged
     ports.  Dynamic port forwardings can also be specified in the configuration file.

     IPv6 addresses can be specified by enclosing the address in square brackets.  Only the superuser can forward privileged ports.
     By default, the local port is bound in accordance with the GatewayPorts setting.  However, an explicit bind_address may be used
     to bind the connection to a specific address.  The bind_address of “localhost” indicates that the listening port be bound for
     local use only, while an empty address or ‘*’ indicates that the port should be available from all interfaces.

This would cover my first two requirements pretty well. All I have to run is

ssh -D 1234 user@laptops-ip -C -N

and this opens a SOCKS5 proxy available at localhost:1234 on my local machine. If I update my Firefox settings to use it I can now access my work internal sites without having to install the VPN locally. The downside is now that ALL my Firefox traffic is passing through my work computer. This is where the PAC file comes into play.

# The PAC File

The PAC file is pretty simple. It's a single JavaScript file that exposes a function FindProxyForURL(url, host) that lets Firefox know whether to proxy a request for a url. More information is available via MDN. All that's left to do is to update firefox's settings to use it.

Firefox network settings

Here is my PAC file

const DIRECT = "DIRECT";
const SOCKS = "SOCKS5";

const WORK_URLS = ["*"];

function FindProxyForURL(url, host) {
  if (WORK_URLS.some((h) => shExpMatch(host, h))) {
    return SOCKS;
  return DIRECT;