<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Qubes OS on PrivSec - A practical approach to Privacy and Security</title>
  <link rel="alternate" href="https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/" />
  <link rel="self" href="https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/index.xml" />
  <subtitle>Recent content in Qubes OS on PrivSec - A practical approach to Privacy and Security</subtitle>
  <id>https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/</id>
  <generator uri="http://gohugo.io" version="0.119.0">Hugo</generator>
  <language>en</language>
  <updated>0001-01-01T00:00:00Z</updated>
  <author>
    <name>PrivSec.dev Team</name>
    
  </author>
  <rights>[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)</rights>
      <entry>
        <title>Using IVPN on Qubes OS</title>
        <link rel="alternate" href="https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/using-ivpn-on-qubes-os/" />
        <id>https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/using-ivpn-on-qubes-os/</id>
        <published>2024-05-16T00:00:00Z</published>
        <updated>2025-02-03T08:42:42-07:00</updated>
        <summary type="html">IVPN is a fairly popular and generally trustworthy VPN provider. In this post, I will walk you through how to use the official IVPN client in a ProxyVM on Qubes OS. We will deviate from the official guide by using systemd path to handle DNAT. This will provide the same robustness as their approach to modify /opt/ivpn/etc/firewall.sh, while avoiding the risk that the modifications will be overwritten by a future app update.</summary>
          <content type="html"><![CDATA[<p><img loading="lazy" src="ivpn.png" alt="IVPN"  />
</p>
<p>IVPN is a fairly popular and generally trustworthy VPN provider. In this post, I will walk you through how to use the official IVPN client in a ProxyVM on Qubes OS. We will deviate from the <a href="https://www.ivpn.net/knowledgebase/linux/ivpn-on-qubes-os/">official guide</a> by using systemd path to handle DNAT. This will provide the same robustness as their approach to modify <code>/opt/ivpn/etc/firewall.sh</code>, while avoiding the risk that the modifications will be overwritten by a future app update. We will also be using a TemplateVM for IVPN ProxyVMs instead of using Standalone VMs.</p>
<h2 id="preparing-your-templatevm">Preparing your TemplateVM</h2>
<p>I recommend that you make a new TemplateVM based on the latest Fedora GNOME template and remove all unnecessary packages that you might not use. This way, you can minimize the attack surface while not having to deal with missing dependencies like on a minimal template. With that being said, if you do manage to get the minimal template to fully work with IVPN, feel free to <a href="https://github.com/orgs/PrivSec-dev/discussions">open a discussion on GitHub</a> or <a href="https://tommytran.io/contact">contact me directly</a> and I will update the post accordingly.</p>
<p>I run <a href="https://github.com/TommyTran732/QubesOS-Scripts/blob/main/fedora-gnome/fedora-gnome.sh">this script</a> on my template to trim it down.</p>
<p>Next, you need to create the bind directories for IVPN&rsquo;s configurations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir -p /etc/qubes-bind-dirs.d
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;binds+=( &#39;</span><span class="se">\&#39;</span><span class="s1">&#39;&#39;</span>/etc/opt/ivpn/mutable<span class="s1">&#39;&#39;</span><span class="se">\&#39;</span><span class="s1">&#39; )&#39;</span> <span class="p">|</span> sudo tee /etc/qubes-bind-dirs.d/50_user.conf 
</span></span></code></pre></div><h2 id="installing-the-ivpn-app">Installing the IVPN App</h2>
<p>Inside of the TemplateVM you have just created, do the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo dnf config-manager addrepo --from-repofile<span class="o">=</span>https://repo.ivpn.net/stable/fedora/generic/ivpn.repo
</span></span><span class="line"><span class="cl">sudo dnf install -y ivpn-ui
</span></span></code></pre></div><p>IVPN needs to restart <code>systemd-resolved</code> and run <code>/usr/lib/qubes/qubes-setup-dnat-to-ns</code> every time IVPN modifies <code>/etc/resolv.conf</code>. Create the following files:</p>
<ul>
<li><code>/etc/systemd/system/dnat-to-ns.service</code></li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">Description=Run /usr/lib/qubes/qubes-setup-dnat-to-ns
</span></span><span class="line"><span class="cl">StartLimitIntervalSec=0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Service]
</span></span><span class="line"><span class="cl">Type=oneshot
</span></span><span class="line"><span class="cl">ExecStart=/usr/bin/systemctl restart systemd-resolved
</span></span><span class="line"><span class="cl">ExecStart=/usr/lib/qubes/qubes-setup-dnat-to-ns
</span></span></code></pre></div><ul>
<li><code>/etc/systemd/system/dnat-to-ns.path</code></li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">Description=Run /usr/lib/qubes/qubes-setup-dnat-to-ns when /etc/resolv.conf changes
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Path]
</span></span><span class="line"><span class="cl">PathChanged=/etc/resolv.conf
</span></span><span class="line"><span class="cl">Unit=dnat-to-ns.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Install]
</span></span><span class="line"><span class="cl">WantedBy=multi-user.target
</span></span></code></pre></div><ul>
<li><code>/etc/systemd/system/dnat-to-ns-boot.service</code></li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">Description=Run /usr/lib/qubes/qubes-setup-dnat-to-ns
</span></span><span class="line"><span class="cl">After=qubes-network-uplink.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Service]
</span></span><span class="line"><span class="cl">Type=oneshot
</span></span><span class="line"><span class="cl">ExecStart=sleep 15
</span></span><span class="line"><span class="cl">ExecStart=/usr/lib/qubes/qubes-setup-dnat-to-ns
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Install]
</span></span><span class="line"><span class="cl">WantedBy=multi-user.target
</span></span></code></pre></div><p>Create <code>/etc/systemd/system/systemd-resolved.conf.d/override.conf</code> to disable rate limiting on systemd-resolved restarting:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">StartLimitIntervalSec=0
</span></span></code></pre></div><p>Next, enable the systemd path and service to run at boot:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> dnat-to-ns.path
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> dnat-to-ns-boot.service
</span></span></code></pre></div><p>Finally, shut down the TemplateVM:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo shutdown now
</span></span></code></pre></div><h2 id="creating-the-proxyvm">Creating the ProxyVM</h2>
<p>Create an AppVM based on the TemplateVM you have just created. Set <code>sys-firewall</code> (or whatever FirewallVM you have connected to your <code>sys-net</code>) as the net qube. If you do not have such FirewallVM, use <code>sys-net</code> as the net qube. Next, go to the advanced tab and tick the <code>provides network access to other qubes</code> box.</p>
<p><img loading="lazy" src="provides-network.png" alt="Provides Network"  />
</p>
<p>Open the IVPN and select <code>Settings</code> → <code>DNS</code> → <code>Force management of DNS using resolv.conf</code>.</p>
<p>Go to the <code>IVPN Firewall</code> section and tick the box <code>Allow LAN traffic when IVPN Firewall is enabled</code>. Due to some strange interaction between qubes services and IVPN, certain apps will get internet connections while others do not if this toggle is not enabled. This option will <strong>not</strong> actually allow AppVMs connected to the ProxyVM to connect to the local network.</p>
<p>Enable <code>Always-on firewall</code> to ensure that the killswitch stays on even when the tunnel is disconnected.</p>
<h2 id="additional-assurances">Additional Assurances</h2>
<p>For additional assurances against VPN leaks, you can optionally add these 2 lines to <code>/rw/config/qubes-firewall-user-script</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nft add rule qubes custom-forward oifname eth0 counter drop
</span></span><span class="line"><span class="cl">nft add rule ip6 qubes custom-forward oifname eth0 counter drop
</span></span></code></pre></div><p>This is not strictly necessary, as I have not observed any leaks with the VPN killswitch provided by the app.</p>
<h2 id="notes">Notes</h2>
<p>With this current setup, the ProxyVM you have just created will be responsible for handling Firewall rules for the qubes behind it. This is not ideal, as this is still a fairly large VM, and there is a risk that IVPN or some other apps may interfere with its firewall handling.</p>
<p>Instead, I highly recommend that you <a href="/posts/qubes/firewalling-with-mirageos-on-qubes-os/">create a minimal Mirage FirewallVM</a> and use it as a firewall <strong>behind</strong> the IVPN ProxyVM. Other AppVMs then should use the Mirage Firewall as the net qube instead. This way, you can make sure that firewall rules are properly enforced.</p>
]]></content>
      </entry>
      <entry>
        <title>Using Mullvad VPN on Qubes OS</title>
        <link rel="alternate" href="https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/using-mullvad-vpn-on-qubes-os/" />
        <id>https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/using-mullvad-vpn-on-qubes-os/</id>
        <published>2022-09-03T00:00:00Z</published>
        <updated>2025-02-03T08:42:42-07:00</updated>
        <summary type="html">Mullvad is a fairly popular and generally trustworthy VPN provider. In this post, I will walk you through how to use the official Mullvad client in a ProxyVM on Qubes OS. This method is a lot more convenient than the official guide from Mullvad (which recommends that you manually load in OpenVPN or Wireguard profiles) and will let you seamlessly switch between different location and network setups just as you would on a normal Linux installation.</summary>
          <content type="html"><![CDATA[<p><img loading="lazy" src="mullvad-vpn.png" alt="Mullvad VPN"  />
</p>
<p>Mullvad is a fairly popular and generally trustworthy VPN provider. In this post, I will walk you through how to use the official Mullvad client in a ProxyVM on Qubes OS. This method is a lot more convenient than the <a href="https://mullvad.net/en/help/qubes-os-4-and-mullvad-vpn/">official guide</a> from Mullvad (which recommends that you manually load in OpenVPN or Wireguard profiles) and will let you seamlessly switch between different location and network setups just as you would on a normal Linux installation.</p>
<h2 id="preparing-your-templatevm">Preparing your TemplateVM</h2>
<p>I recommend that you make a new TemplateVM based on the latest Fedora GNOME template and remove all unnecessary packages that you might not use. This way, you can minimize the attack surface while not having to deal with missing dependencies like on a minimal template. With that being said, if you do manage to get the minimal template to fully work with Mullvad, feel free to <a href="https://github.com/orgs/PrivSec-dev/discussions">open a discussion on GitHub</a> or <a href="https://tommytran.io/contact">contact me directly</a> and I will update the post accordingly.</p>
<p>I run <a href="https://github.com/TommyTran732/QubesOS-Scripts/blob/main/fedora-gnome/fedora-gnome.sh">this script</a> on my template to trim it down.</p>
<p>Next, you need to create the bind directories for Mullvad&rsquo;s configurations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir -p /etc/qubes-bind-dirs.d
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;binds+=( &#39;</span><span class="se">\&#39;</span><span class="s1">&#39;&#39;</span>/etc/mullvad-vpn<span class="s1">&#39;&#39;</span><span class="se">\&#39;</span><span class="s1">&#39; )&#39;</span> <span class="p">|</span> sudo tee /etc/qubes-bind-dirs.d/50_user.conf 
</span></span></code></pre></div><h2 id="installing-the-mullvad-app">Installing the Mullvad App</h2>
<p>Inside of the TemplateVM you have just created, do the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo dnf config-manager addrepo --from-repofile<span class="o">=</span>https://repository.mullvad.net/rpm/stable/mullvad.repo
</span></span><span class="line"><span class="cl">sudo dnf install -y mullvad-vpn
</span></span></code></pre></div><p>To work around <a href="https://github.com/mullvad/mullvadvpn-app/issues/3803">issue 3803</a>, we will be using systemd path to run <code>/usr/lib/qubes/qubes-setup-dnat-to-ns</code> every time Mullvad modifies <code>/etc/resolv.conf</code>. Create the following files:</p>
<ul>
<li><code>/etc/systemd/system/dnat-to-ns.service</code></li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">Description=Run /usr/lib/qubes/qubes-setup-dnat-to-ns
</span></span><span class="line"><span class="cl">StartLimitIntervalSec=0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Service]
</span></span><span class="line"><span class="cl">Type=oneshot
</span></span><span class="line"><span class="cl">ExecStart=/usr/bin/systemctl restart systemd-resolved
</span></span><span class="line"><span class="cl">ExecStart=/usr/lib/qubes/qubes-setup-dnat-to-ns
</span></span></code></pre></div><ul>
<li><code>/etc/systemd/system/dnat-to-ns.path</code></li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">Description=Run /usr/lib/qubes/qubes-setup-dnat-to-ns when /etc/resolv.conf changes
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Path]
</span></span><span class="line"><span class="cl">PathChanged=/etc/resolv.conf
</span></span><span class="line"><span class="cl">Unit=dnat-to-ns.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Install]
</span></span><span class="line"><span class="cl">WantedBy=multi-user.target
</span></span></code></pre></div><p>Create <code>/etc/systemd/system/systemd-resolved.conf.d/override.conf</code> to disable rate limiting on systemd-resolved restarting:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">StartLimitIntervalSec=0
</span></span></code></pre></div><p>Next, enable the systemd path:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> dnat-to-ns.path
</span></span></code></pre></div><p>Finally, shut down the TemplateVM:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo shutdown now
</span></span></code></pre></div><h2 id="creating-the-proxyvm">Creating the ProxyVM</h2>
<p>Create an AppVM based on the TemplateVM you have just created. Set <code>sys-firewall</code> (or whatever FirewallVM you have connected to your <code>sys-net</code>) as the net qube. If you do not have such FirewallVM, use <code>sys-net</code> as the net qube. Next, go to the advanced tab and tick the <code>provides network access to other qubes</code> box.</p>
<p><img loading="lazy" src="provides-network.png" alt="Provides Network"  />
</p>
<p>Open the Mullvad VPN app. Go to <code>Settings</code> → <code>VPN settings</code> and toggle <code>Local network sharing</code>. Due to some strange interaction between qubes services and Mullvad VPN, certain apps will get internet connections while others do not if this toggle is not enabled. This toggle will <strong>not</strong> actually allow AppVMs connected to the ProxyVM to connect to the local network.</p>
<p>Enable <code>Lockdown mode</code> to ensure that the killswitch stays on even when the tunnel is disconnected.</p>
<h2 id="additional-assurances">Additional Assurances</h2>
<p>For additional assurances against VPN leaks, you can optionally add these 2 lines to <code>/rw/config/qubes-firewall-user-script</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nft add rule qubes custom-forward oifname eth0 counter drop
</span></span><span class="line"><span class="cl">nft add rule ip6 qubes custom-forward oifname eth0 counter drop
</span></span></code></pre></div><p>This is not strictly necessary, as I have not observed any leaks with the VPN killswitch provided by the app.</p>
<h2 id="notes">Notes</h2>
<p>With this current setup, the ProxyVM you have just created will be responsible for handling Firewall rules for the qubes behind it. This is not ideal, as this is still a fairly large VM, and there is a risk that Mullvad or some other apps may interfere with its firewall handling.</p>
<p>Instead, I highly recommend that you <a href="/posts/qubes/firewalling-with-mirageos-on-qubes-os/">create a minimal Mirage FirewallVM</a> and use it as a firewall <strong>behind</strong> the Mullvad ProxyVM. Other AppVMs then should use the Mirage Firewall as the net qube instead. This way, you can make sure that firewall rules are properly enforced.</p>
]]></content>
      </entry>
      <entry>
        <title>Firewalling with MirageOS on Qubes OS</title>
        <link rel="alternate" href="https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/firewalling-with-mirageos-on-qubes-os/" />
        <id>https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/firewalling-with-mirageos-on-qubes-os/</id>
        <published>2022-08-26T00:00:00Z</published>
        <updated>2025-02-03T08:42:42-07:00</updated>
        <summary type="html">MirageOS is a library operating system with which you can create a unikernel for the sole purpose of acting as Qubes OS&amp;rsquo;s firewall. In this post, I will walk you through how to set this up.
Advantages Small attack surface. The unikernel only contains a minimal set of libraries to function, so it has a much smaller attack surface than a general purpose operating system like a Linux distribution or openBSD.</summary>
          <content type="html"><![CDATA[<p><img loading="lazy" src="mirageos.png" alt="MirageOS"  />
</p>
<p><a href="https://mirage.io/">MirageOS</a> is a library operating system with which you can create a unikernel for the sole purpose of acting as Qubes OS&rsquo;s firewall. In this post, I will walk you through how to set this up.</p>
<h2 id="advantages">Advantages</h2>
<ul>
<li>Small attack surface. The unikernel only contains a minimal set of libraries to function, so it has a much smaller attack surface than a general purpose operating system like a Linux distribution or openBSD.</li>
<li>Low resource consumption. You only need about 64MB of RAM for each instance of the Mirage Firewall.</li>
<li>Fast startup time.</li>
</ul>
<h2 id="disadvantages">Disadvantages</h2>
<ul>
<li>No official package for Qubes OS. This means that you need to follow the development process on GitHub and download the new build whenever there is a release.</li>
<li>Does not work well with the Windows PV network driver. With that being said, the Windows PV networking driver is pretty buggy on its own, and I don&rsquo;t recommend that you use it anyways.</li>
</ul>
<h2 id="installing-the-unikernel">Installing the unikernel</h2>
<p>To deploy MirageOS, you need to copy the <code>vmlinuz</code> and <code>initramfs</code> files from their <a href="https://github.com/mirage/qubes-mirage-firewall/releases">releases page</a> to <code>/var/lib/qubes/vm-kernels/mirage-firewall</code> in <code>dom0</code>.</p>
<h3 id="templatevm">TemplateVM</h3>
<p>Create a TemplateVM:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qvm-create <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">kernel</span><span class="o">=</span>mirage-firewall <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">kernelopts</span><span class="o">=</span><span class="s1">&#39;&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">memory</span><span class="o">=</span><span class="m">64</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">maxmem</span><span class="o">=</span><span class="m">64</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">vcpus</span><span class="o">=</span><span class="m">1</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">virt_mode</span><span class="o">=</span>pvh <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --label<span class="o">=</span>black <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --class TemplateVM <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  your_template_name
</span></span></code></pre></div><p>Don&rsquo;t worry if the TemplateVM doesn&rsquo;t launch &mdash; we don&rsquo;t need it to.</p>
<h3 id="disposable-template">Disposable Template</h3>
<p>Next, create a disposable template based on the TemplateVM you have just created.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qvm-create <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">template</span><span class="o">=</span>your_template_name <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">provides_network</span><span class="o">=</span>True <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">template_for_dispvms</span><span class="o">=</span>True <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --label<span class="o">=</span>orange <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --class AppVM <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  your_disposable_template_name
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">qvm-features your_disposable_template_name qubes-firewall <span class="m">1</span>
</span></span><span class="line"><span class="cl">qvm-features your_disposable_template_name no-default-kernelopts <span class="m">1</span>
</span></span></code></pre></div><p>Your disposable templates should now launch and shut down properly.</p>
<h3 id="disposable-firewallvms">Disposable FirewallVMs</h3>
<p>You can now create disposable FirewallVMs based on your disposable template. I recommend replacing <code>sys-firewall</code> with a disposable Mirage firewall. If you use ProxyVMs like <code>sys-whonix</code>, I recommend that you add a disposable Mirage Firewall after the ProxyVM as well, and use it as the net qube for your AppVMs.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qvm-create <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">template</span><span class="o">=</span>your_disposable_template_name <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">provides_network</span><span class="o">=</span>True <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --property <span class="nv">netvm</span><span class="o">=</span>your_net_qube_name <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --label<span class="o">=</span>orange <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --class DispVM <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  your_firwall_name
</span></span></code></pre></div>]]></content>
      </entry>
      <entry>
        <title>Using Split GPG and Split SSH on Qubes OS</title>
        <link rel="alternate" href="https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/using-split-gpg-and-split-ssh-on-qubes-os/" />
        <id>https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/using-split-gpg-and-split-ssh-on-qubes-os/</id>
        <published>2022-08-13T00:00:00Z</published>
        <updated>2025-02-03T08:42:42-07:00</updated>
        <summary type="html">This post will go over setting up Split GPG, then setting up Split SSH with the same PGP keys. Effectively, we are emulating what you can do with a PGP smartcard on Qubes OS.
Split GPG Follow the official Qubes OS documentation to set this up.
Note that if you already have a PGP key with a passphrase, you can remove it by installing pinentry-gtk to vault&amp;rsquo;s TemplateVM, then run gpg2 --edit-key &amp;lt;key_id&amp;gt; and passwd to set an empty passphrase.</summary>
          <content type="html"><![CDATA[<p><img loading="lazy" src="split-gpg-ssh.png" alt="Split GPG &amp;amp; SSH"  />
</p>
<p>This post will go over setting up Split GPG, then setting up Split SSH with the same PGP keys. Effectively, we are emulating what you can do with a PGP smartcard on Qubes OS.</p>
<h2 id="split-gpg">Split GPG</h2>
<p>Follow the official Qubes OS <a href="https://www.qubes-os.org/doc/split-gpg/">documentation</a> to set this up.</p>
<p>Note that if you already have a PGP key with a passphrase, you can remove it by installing <code>pinentry-gtk</code> to <code>vault</code>&rsquo;s TemplateVM, then run <code>gpg2 --edit-key &lt;key_id&gt;</code> and <code>passwd</code> to set an empty passphrase. The default non-graphical pinentry will just make an infinite loop and will not allow you to set an empty passphrase.</p>
<h2 id="split-ssh">Split SSH</h2>
<p>This part is based on the Qubes Community&rsquo;s <a href="https://forum.qubes-os.org/t/split-ssh/19060">guide</a>; however, I will deviate from it to use the PGP keys for SSH instead of generating a new key pair.</p>
<h3 id="in-dom0">In <code>dom0</code></h3>
<ul>
<li>Create <code>/etc/qubes-rpc/policy/qubes.SshAgent</code> with <code>@anyvm @anyvm ask,default_target=vault</code> as the content. Since the keys are not passphrase protected, you should <strong>not</strong> set the policy to allow.</li>
</ul>
<h3 id="in-vault-appvm">In <code>vault</code> AppVM</h3>
<ul>
<li>Add <code>enable-ssh-support</code> to the end of <code>~/.gnupg/gpg-agent.conf</code></li>
<li>Get your keygrip with <code>gpg --with-keygrip -k</code></li>
<li>Add your keygrip to the end of <code>~/.gnupg/sshcontrol</code></li>
</ul>
<p><img loading="lazy" src="keygrip.png" alt="PGP Keygrip"  />
</p>
<h3 id="in-vaults-templatevm">In <code>vault</code>&rsquo;s TemplateVM</h3>
<ul>
<li>Create <code>/etc/qubes-rpc/qubes.SshAgent</code> with the following content:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="c1"># Qubes App Split SSH Script</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Activate GPG Agent and set the correct SSH socket</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">SSH_AUTH_SOCK</span><span class="o">=</span><span class="k">$(</span>gpgconf --list-dirs agent-ssh-socket<span class="k">)</span>
</span></span><span class="line"><span class="cl">gpgconf --launch gpg-agent
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># safeguard - Qubes notification bubble for each ssh request</span>
</span></span><span class="line"><span class="cl">notify-send <span class="s2">&#34;[</span><span class="k">$(</span>qubesdb-read /name<span class="k">)</span><span class="s2">] SSH agent access from: </span><span class="nv">$QREXEC_REMOTE_DOMAIN</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># SSH connection</span>
</span></span><span class="line"><span class="cl">socat - <span class="s2">&#34;UNIX-CONNECT:</span><span class="nv">$SSH_AUTH_SOCK</span><span class="s2">&#34;</span>
</span></span></code></pre></div><ul>
<li>Make it executable with <code>sudo chmod +x /etc/qubes-rpc/qubes.SshAgent</code></li>
<li>Turn off the templateVM. If the <code>vault</code> VM is running, turn it off, then start it to update the VM&rsquo;s configuration.</li>
</ul>
<h3 id="in-ssh-client-appvm">In <code>ssh-client</code> AppVM</h3>
<ul>
<li>Add the following to the end of <code>/rw/config/rc.local</code>:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># SPLIT SSH CONFIGURATION &gt;&gt;&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># replace &#34;vault&#34; with your AppVM name which stores the ssh private key(s)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SSH_VAULT_VM</span><span class="o">=</span><span class="s2">&#34;vault&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$SSH_VAULT_VM</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">export</span> <span class="nv">SSH_SOCK</span><span class="o">=</span><span class="s2">&#34;/home/user/.SSH_AGENT_</span><span class="nv">$SSH_VAULT_VM</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  rm -f <span class="s2">&#34;</span><span class="nv">$SSH_SOCK</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  sudo -u user /bin/sh -c <span class="s2">&#34;umask 177 &amp;&amp; exec socat &#39;UNIX-LISTEN:</span><span class="nv">$SSH_SOCK</span><span class="s2">,fork&#39; &#39;EXEC:qrexec-client-vm </span><span class="nv">$SSH_VAULT_VM</span><span class="s2"> qubes.SshAgent&#39;&#34;</span> <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="c1"># &lt;&lt;&lt; SPLIT SSH CONFIGURATION</span>
</span></span></code></pre></div><ul>
<li>Add the following to the end of <code>~/bash.rc</code>:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># SPLIT SSH CONFIGURATION &gt;&gt;&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># replace &#34;vault&#34; with your AppVM name which stores the ssh private key(s)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SSH_VAULT_VM</span><span class="o">=</span><span class="s2">&#34;vault&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$SSH_VAULT_VM</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">export</span> <span class="nv">SSH_AUTH_SOCK</span><span class="o">=</span><span class="s2">&#34;/home/user/.SSH_AGENT_</span><span class="nv">$SSH_VAULT_VM</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="c1"># &lt;&lt;&lt; SPLIT SSH CONFIGURATION</span>
</span></span></code></pre></div><ul>
<li>Restart <code>ssh-client</code> and confirm if it&rsquo;s working with <code>ssh-add -L</code>.</li>
</ul>
<h3 id="limitations">Limitations</h3>
<p>A malicious <code>ssh-client</code> AppVM can hold onto the ssh-agent connection for more than one use until it is shut down. While your private key is protected, a malicious actor with access to the AppVM can still abuse the ssh-agent to log into your servers.</p>
]]></content>
      </entry>
      <entry>
        <title>Using Lokinet on Qubes OS</title>
        <link rel="alternate" href="https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/using-lokinet-on-qubes-os/" />
        <id>https://deploy-preview-444--privsec-dev.netlify.app/posts/qubes/using-lokinet-on-qubes-os/</id>
        <published>2022-07-27T00:00:00Z</published>
        <updated>2025-02-03T08:42:42-07:00</updated>
        <summary type="html">Lokinet is an Internet overlay network utilizing onion routing to provide anonymity for its users, similar to Tor network. This post will go over how to set it up on Qubes OS.
Before we start&amp;hellip;
This post should not be considered an endorsement of Lokinet in any shape or form. Lokinet is currently not in a good state &amp;mdash; it has not had a public release since 2022, and most free public exit nodes have gone offline.</summary>
          <content type="html"><![CDATA[<p><img loading="lazy" src="lokinet.png" alt="Lokinet"  />
</p>
<p><a href="https://lokinet.org">Lokinet</a> is an Internet overlay network utilizing onion routing to provide anonymity for its users, similar to Tor network. This post will go over how to set it up on Qubes OS.</p>
<p><strong>Before we start&hellip;</strong></p>
<p>This post should not be considered an endorsement of Lokinet in any shape or form. Lokinet is currently not in a good state &mdash; it has not had a public release since 2022, and most free public exit nodes have gone offline. According to the developers, they are doing major rewrites of the code, and it should not be used in production at the moment.</p>
<h2 id="creating-the-templatevm">Creating the TemplateVM</h2>
<p>Currently, the Lokinet client seems to work well with only Debian-based distributions. This means that our template will have to be one of the Debian-based ones. Personally, I use <a href="https://github.com/TommyTran732/QubesOS-Scripts/blob/main/debian-gnome/debian-gnome.sh">this script</a> to trim down the Debian GNOME template and convert it to Kicksecure. Kicksecure reduces the attack surface of Debian with a substantial set of hardening configurations, and a nice feature to go with an anonymity network like Lokinet is <a href="https://www.kicksecure.com/wiki/Boot_Clock_Randomization">Boot Clock Randomization</a> which helps defend against <a href="https://www.whonix.org/wiki/Time_Attacks">time-based denonymization attacks</a>.</p>
<p>Start by creating the bind directories for Lokinet&rsquo;s configurations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir -p /etc/qubes-bind-dirs.d
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;binds+=( &#39;</span><span class="se">\&#39;</span><span class="s1">&#39;&#39;</span>/etc/loki<span class="s1">&#39;&#39;</span><span class="se">\&#39;</span><span class="s1">&#39; )&#39;</span> <span class="p">|</span> sudo tee /etc/qubes-bind-dirs.d/50_user.conf 
</span></span></code></pre></div><p>Next, add the Oxen PGP key and the Lokinet template. We will deviate from the <a href="https://github.com/oxen-io/lokinet/blob/dev/docs/install.md#linux-install">official documentation</a> and pin the PGP key to only be used for this repository:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl --proxy http://127.0.0.1:8082 https://deb.oxen.io/pub.gpg <span class="p">|</span> sudo tee /usr/share/keyrings/oxen.gpg
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;deb [signed-by=/usr/share/keyrings/oxen.gpg] https://deb.oxen.io </span><span class="k">$(</span>lsb_release -sc<span class="k">)</span><span class="s2"> main&#34;</span> <span class="p">|</span> sudo tee /etc/apt/sources.list.d/oxen.list
</span></span></code></pre></div><p>Next, <code>lokinet</code> and <code>resolvconf</code>. <code>lokinet-gui</code> was very buggy when I tested it inside my VM, so I recommend installing only the daemon. <code>resolvconf</code> is used by the Lokinet init script but is not declared as a dependency for some reason, so you have to manually install it as well:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update
</span></span><span class="line"><span class="cl">sudo apt install lokinet-gui resolvconf
</span></span></code></pre></div><p>To work around the problem where Qubes overrides the DNS configuration at boot, create <code>/etc/systemd/system/lokinet-dns-fix.service</code> with the following content:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">Description=Fix DNS for Lokinet
</span></span><span class="line"><span class="cl">After=qubes-network-uplink.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Service]
</span></span><span class="line"><span class="cl">Type=oneshot
</span></span><span class="line"><span class="cl">ExecStart=/usr/bin/rm /etc/resolv.conf
</span></span><span class="line"><span class="cl">ExecStart=/usr/bin/ln -s /run/resolvconf/resolv.conf /etc/resolv.conf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Install]
</span></span><span class="line"><span class="cl">WantedBy=multi-user.target
</span></span></code></pre></div><p>Enable the <code>lokinet-dns-fix</code> service:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> lokinet-dns-fix
</span></span></code></pre></div><p>At this stage, you can install any .deb app you want to use with Lokinet in the TemplateVM. I have been unable to get DNS working properly with Lokinet as a network VM, so for now we will have to use a Lokinet in each individual AppVM.</p>
<p>Finally, shut down the TemplateVM:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo shutdown now
</span></span></code></pre></div><h2 id="creating-the-appvm">Creating the AppVM</h2>
<p>Create an AppVM based on the TemplateVM you have just created. Set <code>sys-firewall</code> (or whatever FirewallVM you have connected to your <code>sys-net</code>) as the net qube. If you do not have such FirewallVM, use <code>sys-net</code> as the net qube.</p>
<p>Edit <code>/etc/loki/lokinet.ini</code> and add the exit node you want to use. At the moment, the only free exit node that I am aware of is <code>euroexit.loki</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[network]
</span></span><span class="line"><span class="cl">exit-node=euroexit.loki
</span></span></code></pre></div>]]></content>
      </entry>

</feed>


