158 lines
17 KiB
HTML
158 lines
17 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
<title>
|
|
Proxy Auto-Configuration (PAC) is actually useful
|
|
- rizaldy.today
|
|
</title>
|
|
|
|
<link rel="me" href="https://edgy.social/@rizaldy">
|
|
|
|
<link rel="preconnect" href="https://unpkg.com">
|
|
<link rel="preconnect" href="https://api.fontshare.com">
|
|
<link rel="preconnect" href="https://fonts.bunny.net">
|
|
|
|
<link href="https://unpkg.com/prismjs@1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
|
|
<link href="https://api.fontshare.com/v2/css?f[]=clash-display@1&f[]=sentient@1&display=swap" rel="stylesheet">
|
|
<link href="https://fonts.bunny.net/css?family=jetbrains-mono:400" rel="stylesheet">
|
|
|
|
<link href="/assets/css/reset.css" rel="stylesheet">
|
|
<link href="/assets/css/style.css" rel="stylesheet">
|
|
</head>
|
|
|
|
|
|
<body>
|
|
<main class="l-container">
|
|
<div class="l-fragment l-fragment--blog">
|
|
<div class="c-article">
|
|
<h1>Proxy Auto-Configuration (PAC) is actually useful</h1>
|
|
|
|
<p class="c-article__meta-info">
|
|
<time>Jul 05, 2023</time> •
|
|
|
|
|
|
|
|
|
|
<span class="c-article__meta-info-tag"
|
|
><a class="u-no-underline u-underline--hover" href="#tailscale">TAILSCALE</a></span
|
|
>
|
|
|
|
|
|
|
|
<span class="c-article__meta-info-tag"
|
|
><a class="u-no-underline u-underline--hover" href="#proxy">PROXY</a></span
|
|
>
|
|
|
|
|
|
</p>
|
|
|
|
<p>So I just got my new Mac machine last week. It's just a small machine with 8 CPUs and 8 GB of memory. I've been thinking about buying one for a long time and now the time has come!</p>
|
|
<p>I'll be using this tiny PC as my main workstation, but unlike my previous approach of setting up development environments, I now have plans to isolate each existing environment into VMs,
|
|
like, one for private; one for side projects, one for community stuff, and one for work.</p>
|
|
<p>I want to keep this tiny PC as clean as possible.</p>
|
|
<h2>The dumb way to use Tailscale</h2>
|
|
<p>This is the main topic of this post, but I'm doing my best to make this post not feel like a shot tweet. On my laptop I use 4 different identities (accounts) in Tailscale,
|
|
and I used to use Tailscale's <a href="https://tailscale.com/blog/fast-user-switching/">Fast user switching</a> feature which was quite useful. Often I forget to switch back
|
|
to my personal account after I realize I can't SSH into my server at home via its hostname.</p>
|
|
<p>So here's the story: I don't want to "switch" anymore so I don't forget one more time.</p>
|
|
<p>I create every VM I need and use different identities for Tailscale there. The OS is NixOS and I'm using <a href="https://orbstack.dev">OrbStack</a> to provision VMs on my tiny PC.
|
|
When I'm not working from home, I can SSH into the VM with this tiny PC as a jumphost while hoping my internet at home is working fine.</p>
|
|
<h3>First, it was DNS</h3>
|
|
<p>Every <a href="https://tailscale.com/kb/1136/tailnet/">Tailnet</a> has its own <a href="https://tailscale.com/kb/1217/tailnet-name/">unique name</a> with <code>ts.net</code> as root domains. My tiny PC's hostname
|
|
is <code>mac-mini</code> (sounds boring) so I can access it via <code>mac-mini.duck-map.ts.net</code>, and yes, duck-map.ts.net is my <em>real</em> Tailnet name.</p>
|
|
<p>The first problem is that <code>ts.net</code> can only be resolved by the "MagicDNS server" which resides on your own device accessed via <code>100.100.100.100</code>. This means that
|
|
when you try to query names for a machine that is outside your tailnet, you will get a bogus NXDOMAIN — which is good.</p>
|
|
<p>The second problem is that you can't route packets to machines outside your tailnet, of course.</p>
|
|
<p>Maybe I could use <a href="https://tailscale.com/kb/1019/subnets/">Subnet Routers</a> to advertise subnets of the bridge interface used by my VM but that only solves half of the problem (excluding DNS queries).</p>
|
|
<p>And what about the <a href="https://tailscale.com/kb/1103/exit-nodes/">Exit Node</a> option? Of course not the answer.</p>
|
|
<h3>Then, it was routing tables</h3>
|
|
<p>In certain cases I had to access an internal application that was only accessible through Tailscale to troubleshoot (#sysadminlife). I don't use proxies much but when I do my favorite
|
|
is to <code>ssh -D 6669 somewhere</code> and use 127.0.0.1:6669 as SOCKS5 proxy servers.</p>
|
|
<p><img src="./236D9096-4D0A-43A5-9827-6227230FB8FE.jpg" alt="236D9096-4D0A-43A5-9827-6227230FB8FE.jpg"></p>
|
|
<p>From the screenshot above, <code>kudxxx.tailnet-xxxxx12.ts.net</code> is the machine that resides on the tailnet where I work. I can't resolve the name, because, well, I'm using my own personal tailnet.</p>
|
|
<p>Then I can use <code>ssh -D 6669 delman@orb</code> where <code>delman</code> is the name of the VM to the tailnet at work. <code>socks5h</code> indicates that the DNS query is made on SOCKS5 proxy servers.</p>
|
|
<p>If referring to the screenshot above, I think it works.</p>
|
|
<h3>Proxy Auto-Configuration</h3>
|
|
<p>What if I need to access different machines on different tailnets like <code>heavy-rotation.duck-map.ts.net</code> and <code>kudxxx.tailnet-xxxxx12.ts.net</code> at the same time?</p>
|
|
<p>On MacOS (or maybe other OS too) you can only use 1 proxy server on your machine at a time. So <code>ssh -D 6669 delman@orb</code> and <code>ssh -D 4848 heavy-rotation@orb</code> require extra work when
|
|
you need to use either one.</p>
|
|
<p><img src="./AF9155DC-0B3D-4A4C-9F64-7435D6F77738.jpg" alt="./AF9155DC-0B3D-4A4C-9F64-7435D6F77738.jpg"></p>
|
|
<p>And no, using transparent proxies doesn't help.</p>
|
|
<p>And then I just came across an old technology called <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file">Proxy Auto-Configuration</a>
|
|
which is the title of this post. The concept is actually simple: a PAC is just a JavaScript file that calls a <code>FindProxyForURL</code> function that returns a single string. The minimal
|
|
script is something like this:</p>
|
|
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">FindProxyForURL</span> <span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> host</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">'url '</span> <span class="token operator">+</span> url<span class="token punctuation">)</span><br> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">'host '</span> <span class="token operator">+</span> host<span class="token punctuation">)</span><br><br> <span class="token keyword">return</span> <span class="token string">'DIRECT'</span><br><span class="token punctuation">}</span></code></pre>
|
|
<p>You don't need to save it using <code>.js</code> extensions by the way.</p>
|
|
<p>As far as I know that <code>alert</code> function doesn't work in Safari but it works fine in Firefox and Chrome. This is how it looks when "debugging the PAC" using Firefox:</p>
|
|
<p><img src="./1A5B132F-D2F9-4B1F-9923-434B045CFF60.jpg" alt="./1A5B132F-D2F9-4B1F-9923-434B045CFF60.jpg"></p>
|
|
<p>In many cases, you don't need to do that just to verify if your PAC is working — checking the access.log where you hosted the pac file must be enough.</p>
|
|
<h3>Making it official</h3>
|
|
<p>Now, here is the strategy:</p>
|
|
<ul>
|
|
<li>If I access <code>.duck-map.ts.net</code>, proxy the requests to <code>127.0.0.1:4848</code></li>
|
|
<li>If I access <code>.tailnet-xxxxx12.ts.net</code>, proxy the requests to <code>127.0.0.1:6669</code></li>
|
|
<li>Other than that don't proxy the requests to anywhere</li>
|
|
</ul>
|
|
<p>Actually I can use <code>mac-mini</code> instead of <code>127.0.0.1</code> as the hostname so I can use the PAC file everywhere using the same URL.</p>
|
|
<p>In every VM I use <a href="https://github.com/ginuerzh/gost"><code>gost</code></a> as SOCKS5 server. I can create a simple systemd service for <code>gost</code> like this:</p>
|
|
<pre class="language-js"><code class="language-js">systemd<span class="token punctuation">.</span>services<span class="token punctuation">.</span>gost <span class="token operator">=</span> <span class="token punctuation">{</span><br> description <span class="token operator">=</span> <span class="token string">"gost"</span><span class="token punctuation">;</span><br><br> after <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"network.target"</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br> wantedBy <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"multi-user.target"</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br><br> serviceConfig <span class="token operator">=</span> <span class="token punctuation">{</span><br> ExecStart <span class="token operator">=</span> <span class="token string">"${pkgs.gost}/bin/gost -L=:6669"</span><span class="token punctuation">;</span><br> Restart <span class="token operator">=</span> <span class="token string">"always"</span><span class="token punctuation">;</span><br> RestartSec <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><br> <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
|
|
<p>And then <code>nixos-rebuild switch</code> as usual, then check:</p>
|
|
<pre class="language-bash"><code class="language-bash">$ systemctl status gost<br><br>● gost.service - gost<br> Loaded: loaded <span class="token punctuation">(</span>/etc/systemd/system/gost.service<span class="token punctuation">;</span> enabled<span class="token punctuation">;</span> preset: enabled<span class="token punctuation">)</span><br> Drop-In: /nix/store/rmhm2f4izkfxkpaix0ca2pxnvyswkxfi-system-units/service.d<br> └─zzz-lxc-service.conf<br> Active: active <span class="token punctuation">(</span>running<span class="token punctuation">)</span> since Wed <span class="token number">2023</span>-07-05 <span class="token number">15</span>:57:51 WIB<span class="token punctuation">;</span> 8h ago<br> Main PID: <span class="token number">43857</span> <span class="token punctuation">(</span>gost<span class="token punctuation">)</span><br> IP: <span class="token number">16</span>.1M in, <span class="token number">15</span>.8M out<br> IO: 0B read, 0B written<br> Tasks: <span class="token number">11</span> <span class="token punctuation">(</span>limit: <span class="token number">7106</span><span class="token punctuation">)</span><br> Memory: <span class="token number">10</span>.5M<br> CPU: <span class="token number">4</span>.504s<br> CGroup: /system.slice/gost.service<br> └─43857 /nix/store/q34c64p4cnxh67yxsqxjpjsgdmg8ilpq-gost-2.11.5/bin/gost <span class="token parameter variable">-L</span><span class="token operator">=</span>:6669</code></pre>
|
|
<p>For the contents of the PAC file, it could be like this:</p>
|
|
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">FindProxyForURL</span> <span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> host</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br> work <span class="token operator">=</span> <span class="token string">"SOCKS5 mac-mini:6669"</span><br> community <span class="token operator">=</span> <span class="token string">"SOCKS5 mac-mini:4848"</span><br><br> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shExpMatch</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> <span class="token string">"*.tailnet-xxxxx12.ts.net"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br> <span class="token keyword">return</span> work<br> <span class="token punctuation">}</span><br><br> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shExpMatch</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> <span class="token string">"*.duck-map.ts.net"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br> <span class="token keyword">return</span> community<br> <span class="token punctuation">}</span><br><br> <span class="token keyword">return</span> <span class="token string">"DIRECT"</span><br><span class="token punctuation">}</span></code></pre>
|
|
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#shexpmatch"><code>shExpMatch</code></a> is a function to check if the string
|
|
matches a specified shell glob expression. When one of the conditions is met, it tells the client <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#return_value_format">how to connect</a> to the target.</p>
|
|
<p><a href="./F89B89ED-50B6-4A0C-B6C2-078BFE35CCE6.jpg"><img src="./F89B89ED-50B6-4A0C-B6C2-078BFE35CCE6.jpg" alt="./F89B89ED-50B6-4A0C-B6C2-078BFE35CCE6.jpg"></a></p>
|
|
<p><small><center><em>*click image above to enlarge*</em></center></small></p>
|
|
<p>As you may see I can access <code>*.duck-map.ts.net</code> and <code>*.tailnet-xxxxx12.ts.net</code> at the same time without switching accounts. If you check <code>(1)</code> and <code>(2)</code> in the screenshot above, the value in "remote address" is where the proxy server is running.</p>
|
|
<h3>But why not share nodes?</h3>
|
|
<p>I can <a href="https://tailscale.com/kb/1084/sharing/">share</a> my <code>mac-mini</code> devices to every tailnet I want, but why not? I don't know, maybe the answer is the same as why I installed and used a different account on Tailscale, in different VMs.</p>
|
|
<p>Also, "shared devices" are <em>quarantined</em> by default. Which means my <code>mac-mini</code> can't initiate connections to devices on the "shared network" until they talk to it first — although it's not a big deal.</p>
|
|
<h3>Why not Tailscale Funnel?</h3>
|
|
<p>It's a different story. Tailscale Funnel is all about exposing devices to the <em>wider</em> internet. This means that even anyone with no Tailscale installed can access
|
|
(usually a web service) via the boring HTTPS protocol. No MagicDNS. No CGNAT IPs. Just the internet we are used to.</p>
|
|
<h2>Conclusion</h2>
|
|
<p>There is no way to verify the integrity of the PAC file especially if you load it via a remote address using an insecure procotol. Which means MiTM attacks are by no means impossible. Maybe someone
|
|
is snooping on your network especially if you are on a public network that uses a captive portal. There's nothing to stop anyone from modifying a PAC file if <em>they</em> want and can.</p>
|
|
<p>But it's worth noting that almost all traffic in 2023 uses end-to-end encryption via HTTPS protocols. If you're installing a "CA certificate" because someone out of nowhere
|
|
asked you to do so, don't do it. If it's too late, maybe consider stopping receiving candy from random people at the bar as well, if that happens to you.</p>
|
|
<p>A simple <code>python -m http.server</code> or <code>caddy:alpine</code> web server can help serve your PAC files on the machine you control. And since you're in control, you probably already have Tailscale installed on the device and can
|
|
use a secure transport (such as Wireguard protocols) to load the PAC file.</p>
|
|
<p>The Proxy Auto-Configuration was <a href="https://developer.mozilla.org/en-US/docs/web/http/proxy_servers_and_tunneling/proxy_auto-configuration_pac_file#history_and_implementation">introduced</a> into Netscape Navigator 2.0 in the late 1990s
|
|
at the same time when JavaScript was introduced. For years I have wondered why my machine has "Automatic proxy configuration" options and why I would ever need it.</p>
|
|
<p>And now I know.</p>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<footer class="c-footer u-clearfix">
|
|
<div class="c-footer__copyleft">
|
|
<p class="u-text-left">
|
|
© MMXXIII Rizaldy. Any and all opinions listed here are my own and not representative of my
|
|
employers; future, past and present.
|
|
</p>
|
|
</div>
|
|
<div class="c-footer__links">
|
|
<ul>
|
|
<li><a class="u-no-underline u-underline--hover" href="/">About</a></li>
|
|
<li><a class="u-no-underline u-underline--hover" href="/blog">Writings</a></li>
|
|
<li><a class="u-no-underline u-underline--hover" href="/colophon">Colophon</a></li>
|
|
<li>
|
|
<a class="u-no-underline u-underline--hover" href="https://github.com/faultables/rizaldy.today/commit/419894b"
|
|
>Source Code (419894b)</a
|
|
>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</footer>
|
|
|
|
</main>
|
|
|
|
|
|
</body>
|
|
</html>
|