├── .gitignore ├── Makefile ├── README.md ├── TODO ├── doc ├── Qubes VPN filtering rules.dia ├── Qubes VPN filtering rules.png ├── Qubes VPN.dia └── Qubes VPN.png ├── qubes-vpn.spec └── src ├── etc ├── sudoers.d │ └── qubes-vpn.in └── xdg │ └── autostart │ └── qubes-vpn-notifier.desktop.in └── usr ├── bin └── qubes-vpn-configurator.in ├── lib └── systemd │ ├── system-preset │ └── 78-qubes-vpn.preset │ └── system │ ├── qubes-vpn-configuration.path.in │ ├── qubes-vpn-configuration.service.in │ ├── qubes-vpn-forwarding.service.in │ └── qubes-vpn.service.in ├── libexec ├── qubes-vpn-config-change-detector.in └── qubes-vpn-notifier.in ├── sbin ├── qubes-vpn-forwarding.in └── qubes-vpn-interface-control.in └── share ├── applications └── qubes-vpn-configurator.desktop.in └── icons └── hicolor └── 48x48 └── apps └── qubes-vpn.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | *.tar.gz 4 | *.rpm 5 | src/usr/lib/systemd/system/qubes-vpn-forwarding.service 6 | src/usr/lib/systemd/system/qubes-vpn.service 7 | src/usr/sbin/qubes-vpn-forwarding 8 | src/usr/sbin/qubes-vpn-interface-control 9 | src/usr/bin/qubes-vpn-configurator 10 | src/usr/bin/qubes-vpn-notifier 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROGNAME=qubes-vpn 2 | BINDIR=/usr/bin 3 | SBINDIR=/usr/sbin 4 | UNITDIR=/usr/lib/systemd/system 5 | LIBEXECDIR=/usr/libexec 6 | DATADIR=/usr/share 7 | PRESETDIR=/usr/lib/systemd/system-preset 8 | SYSCONFDIR=/etc 9 | VPNCONFDIR=/rw/config/qubes-vpn 10 | VPNCONFFILE=qubes-vpn.conf 11 | VPNRUNDIR=/var/run/qubes-vpn 12 | QUBESSERVICEDIR=/var/run/qubes-service 13 | DESTDIR= 14 | 15 | objlist = src/usr/sbin/qubes-vpn-interface-control \ 16 | src/usr/sbin/qubes-vpn-forwarding \ 17 | src/usr/lib/systemd/system/qubes-vpn.service \ 18 | src/usr/lib/systemd/system/qubes-vpn-forwarding.service \ 19 | src/usr/libexec/qubes-vpn-notifier \ 20 | src/usr/bin/qubes-vpn-configurator \ 21 | src/usr/libexec/qubes-vpn-config-change-detector \ 22 | src/usr/share/applications/qubes-vpn-configurator.desktop \ 23 | src/usr/lib/systemd/system/qubes-vpn-configuration.path \ 24 | src/usr/lib/systemd/system/qubes-vpn-configuration.service \ 25 | src/etc/xdg/autostart/qubes-vpn-notifier.desktop \ 26 | src/etc/sudoers.d/qubes-vpn 27 | 28 | all: $(objlist) 29 | 30 | clean: 31 | find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f 32 | rm -f *.tar.gz *.rpm 33 | rm -f $(objlist) 34 | 35 | dist: clean 36 | DIR=$(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec` && FILENAME=$$DIR.tar.gz && tar cvzf "$$FILENAME" --exclude "$$FILENAME" --exclude .git --exclude .gitignore -X .gitignore --transform="s|^|$$DIR/|" --show-transformed * 37 | 38 | rpm: dist 39 | T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ta $(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/RPMS/*/* "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T" 40 | 41 | srpm: dist 42 | T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ts $(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T" 43 | 44 | src/%: src/%.in 45 | cat $< | sed 's|@SBINDIR@|$(SBINDIR)|g' | sed 's|@BINDIR@|$(BINDIR)|g' | sed 's|@LIBEXECDIR@|$(LIBEXECDIR)|g' | sed 's|@VPNCONFDIR@|$(VPNCONFDIR)|g' | sed 's|@VPNCONFFILE@|$(VPNCONFFILE)|g ' | sed 's|@VPNRUNDIR@|$(VPNRUNDIR)|g ' | sed 's|@QUBESSERVICEDIR@|$(QUBESSERVICEDIR)|g ' > $@ 46 | 47 | install: all 48 | install -Dm 755 src/usr/sbin/qubes-vpn-forwarding -t $(DESTDIR)/$(SBINDIR)/ 49 | install -Dm 755 src/usr/sbin/qubes-vpn-interface-control -t $(DESTDIR)/$(SBINDIR)/ 50 | install -Dm 755 src/usr/libexec/qubes-vpn-notifier -t $(DESTDIR)/$(LIBEXECDIR)/ 51 | install -Dm 755 src/usr/bin/qubes-vpn-configurator -t $(DESTDIR)/$(BINDIR)/ 52 | install -Dm 755 src/usr/libexec/qubes-vpn-config-change-detector -t $(DESTDIR)/$(LIBEXECDIR)/ 53 | install -Dm 644 src/usr/lib/systemd/system/*.service -t $(DESTDIR)/$(UNITDIR)/ 54 | install -Dm 644 src/usr/lib/systemd/system/*.path -t $(DESTDIR)/$(UNITDIR)/ 55 | install -Dm 644 src/usr/share/icons/hicolor/48x48/apps/qubes-vpn.png -t $(DESTDIR)/$(DATADIR)/icons/hicolor/48x48/apps/ 56 | install -Dm 644 src/usr/share/applications/qubes-vpn-configurator.desktop -t $(DESTDIR)/$(DATADIR)/applications/ 57 | install -Dm 644 src/usr/lib/systemd/system-preset/*.preset -t $(DESTDIR)/$(PRESETDIR)/ 58 | install -Dm 440 src/etc/sudoers.d/qubes-vpn -t $(DESTDIR)/$(SYSCONFDIR)/sudoers.d/ 59 | install -Dm 644 src/etc/xdg/autostart/qubes-vpn-notifier.desktop -t $(DESTDIR)/$(SYSCONFDIR)/xdg/autostart/ 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leakproof Qubes OS VPN 2 | 3 | This package allows you to set up a leakproof OpenVPN VM on your Qubes OS system. 4 | All VMs attached to the VPN VM are automatically and transparently 5 | routed through the VPN. DNS requests do not hit the NetVM — they get routed 6 | through the VPN instead. Connection and disconnection events are notified 7 | using the desktop notification system. When the VPN connection is lost, 8 | traffic is automatically blackholed without any intervention. All system 9 | state changes during VPN operation are (a) volatile (b) minimal (c) 10 | non-interfering with normal Qubes OS ProxyVM operation. 11 | 12 | ![Qubes VPN](doc/Qubes%20VPN.png?raw=true "Qubes VPN") 13 | 14 | ## Installation 15 | 16 | To install the software: 17 | 18 | * Clone this repository. 19 | * Make the RPM on the folder of your clone 20 | `make rpm` 21 | * Copy the RPM to your Qubes OS template: 22 | `qvm-copy-to-vm fedora-23 /path/to/qubes-vpn*.noarch.rpm` 23 | * Install the RPM on the template: 24 | `dnf install /path/to/qubes-vpn*.noarch.rpm` 25 | * Power off the template. 26 | 27 | ## Setup 28 | 29 | ### Create your VPN VM 30 | 31 | Use the Qubes Manager to create a new ProxyVM, which will serve as 32 | the VPN VM (we'll refer to it as the VPN VM from this point on). 33 | Select your system's ProxyVM as the NetVM of the VPN VM, so you can 34 | control the traffic that the VPN VM generates. 35 | 36 | (Note: you could also attach the VPN VM directly to your system's 37 | NetVM, which will work, but you won't be able to firewall the 38 | VPN VM as instructed by the next section. Your call.) 39 | 40 | ### Firewall your VPN VM 41 | 42 | Open the *Firewall rules* tab of your new VPN VM's preferences page. 43 | 44 | *Deny network access* except for *Allow DNS queries*. If the VPN server 45 | is just an IP address (check the configuration given you by the VPN provider) 46 | then you do not have to *Allow DNS queries* at all. 47 | 48 | Add a single rule: 49 | 50 | * Address: either `*` (all hosts) as address (use this when you do not 51 | know the IP address of the VPN server in advance, and all you have is 52 | a DNS host name), or the fixed VPN IP address (if your VPN configuration 53 | has a fixed IP address). 54 | * Protocol: choose the protocol that your VPN server configuration indicates 55 | (TCP or UDP). 56 | * Port number: type in the port number of your VPN server (with OpenVPN, 57 | it's typically 1194, 5000 or 443, but refer to your VPN configuration). 58 | 59 | ### Add the Qubes VPN service to your VPN VM 60 | 61 | Move to the Services tab. Add a service `qubes-vpn` to the list, and ensure 62 | that the checkbox next to the service is checked. Without that service in 63 | this list, the VPN will not start. 64 | 65 | Click OK to close the dialog and save your configuration. 66 | 67 | Optionally, add the *Qubes VPN configurator* program to the menu of your 68 | VPN VM. In the main menu, look for your VPN VM, then select 69 | *Add more shortcuts*, where you will be able to find and add the VPN 70 | configurator icon to your menu. 71 | 72 | ### Setup your VPN configuration 73 | 74 | Launch the program `qubes-vpn-configurator` on the VPN VM (this will be 75 | easy to do if you added the *Qubes VPN configurator* program to the 76 | menu of your VPN VM). This program will let you edit your VPN 77 | configuration and help you place any credential files in the right 78 | places. 79 | 80 | Once you are done, save the file and close the editor. 81 | 82 | At this point, the VPN should start running in the VPN VM. 83 | 84 | You can troubleshoot the VPN service by looking at the output of 85 | `sudo journalctl -fab` on your VPN VM in real time. 86 | 87 | ### Test your changes 88 | 89 | Create a temporary AppVM, attaching it to your new VPN VM. 90 | 91 | Open a terminal in your temporary AppVM. Both VMs will start up. 92 | 93 | You should now be able to ping hosts from the AppVM, as the 94 | VPN VM has established the connection to your VPN server. 95 | 96 | You should also be able to verify with `sudo tcpdump` in the VPN VM 97 | that traffic from the AppVM does not exit in any way through 98 | the `eth0` network interface of the VPN VM. Even when you stop 99 | the VPN service with `sudo service qubes-vpn stop`. 100 | 101 | After your tests succeed, shut off and destroy your temporary AppVM. 102 | 103 | ## Usage 104 | 105 | Attach as many ProxyVMs and AppVMs to the VPN VM as you desire. 106 | As you start them, the VPN VM will start up automatically, and it 107 | will notify you (on the notification area) that a connection has 108 | been established, as well as which route and DNS servers are 109 | being used. When the connection is lost, traffic will be 110 | automatically blackholed to protect your privacy, and you will 111 | be notified of that event. 112 | 113 | Since the VPN VM is a ProxyVM, the firewall rules on AppVMs 114 | attached to it should work fine. 115 | 116 | For additional security (you *are* running a daemon as root 117 | on the VPN VM!) you can interpose an additional ProxyVM 118 | between your VPN VM and your AppVM. 119 | 120 | **Security note**: firewall rules on AppVMs attached to the VPN VM 121 | are enforced by the VPN VM itself. Placing firewall rules on the 122 | VPN VM to control traffic coming from those AppVMs will have no 123 | effect, as those rules can only influence traffic coming from the 124 | VPN software, since traffic from the AppVMs is already encapsulated 125 | in the VPN protocol. 126 | 127 | ![Qubes VPN filtering rules](doc/Qubes VPN filtering rules.png?raw=true "Qubes VPN filtering rules") 128 | 129 | **Security note**: DNS requests from the AppVMs attached to the 130 | VPN VM will go strictly to the VPN provider's DNS servers, and 131 | never to the DNS servers configured on the Qubes NetVM. DNS 132 | requests initiated by the VPN VM itself (e.g. requests to resolve 133 | the VPN endpoint's address to a set of IPs to connect to) will 134 | go strictly to the NetVM attached to the VPN VM, and then to 135 | the DNS servers that the NetVM is using. 136 | 137 | ### Updates of template VMs attached to the VPN VM 138 | 139 | Template VMs attempt to contact the Qubes updates proxy when 140 | performing updates. Since (1) the Qubes updates proxy is usually 141 | your NetVM, (2) the VPN VM is behind the NetVM, (3) traffic from 142 | VMs attached to the VPN VM will only ever be routed through the 143 | VPN, that leads us to a simple conclusion: updates will fail to 144 | contact the NetVM's Qubes updates proxy, and therefore will 145 | fail to be applied. 146 | 147 | The fix is simple: you must set up a Qubes updates proxy in 148 | your VPN VM. 149 | 150 | In the *Services* tab of your VPN VM's properties 151 | dialog, add the service `qubes-updates-proxy`, and ensure 152 | its checkbox is checked. After restarting the VPN VM, 153 | template VMs (with the right firewall rule *Allow connections 154 | to Updates Proxy*) will have automatic access to the updates 155 | proxy, and updates will work fine. Note that update requests 156 | will skip the VPN completely, and will be routed directly 157 | through the network that the VPN uses to transmit and 158 | receive VPN traffic instead. 159 | 160 | ## Theory of operation 161 | 162 | Qubes VPN makes a fairly small set of runtime modifications to the state of the ProxyVM where it runs, which interfere the least with Qubes OS-specific state, when compared with other VPN solutions for Qubes OS. Here they are: 163 | 164 | * The activation of `qubes-iptables.service` (on very early boot, right when the base firewall is initially set up) triggers the activation of `qubes-vpn-forwarding on`. This sets up the steady state: all AppVM traffic goes to routing table 78, and routing on table 78 is 100% blackholed. 165 | * OpenVPN `up` event calls `qubes-vpn-forwarding setuprouting`. This adds the routes that OpenVPN wants to table 78. Then, OpenVPN `up` directs the firewall to route AppVM DNS requests to the VPN DNS servers. Before `up`, all AppVM packets, including DNS, get blackholed. After `up`, they are sent strictly over the VPN. 166 | * OpenVPN `down` calls `qubes-vpn-forwarding blackhole`. Blackhole mode simply removes all table 78 routes that aren't the blackhole route, reverting to the steady state set by `qubes-vpn-forwarding on`. This ends any routing on table 78, and therefore traffic from all AppVMs. It is worth noting that, even if these routing rules were to not be deleted they do automatically go away, when the TUN/TAP device goes down, thus no routing would happen anyway. 167 | * `qubes-vpn-forwarding off` is never called except when qubes-iptables service is reloaded on the ProxyVM (this does not happen unless you do it by hand). 168 | 169 | Among the things that Qubes VPN does *not* do for security reasons are: 170 | 171 | * mucking with, or allowing VPN software to muck with, the system routing tables (risky, could lead traffic from the ProxyVM to go where it shouldn't), 172 | * altering any firewall rules that may be reloaded or flushed by Qubes OS subsystems (comes with the possiblity for leaks). 173 | 174 | ## Troubleshooting and help 175 | 176 | Within the VPN VM: 177 | 178 | ``` 179 | sudo systemctl status qubes-vpn.service 180 | sudo systemctl status qubes-vpn-forwarding.service 181 | ``` 182 | 183 | will give you diagnostic information. 184 | 185 | You can also observe the log of the system in realtime with 186 | `sudo journalctl -fab` as it attempts to connect or 187 | disconnect. 188 | 189 | If you need more debugging information, you can 190 | make the VPN interface control script spit large amounts of 191 | information by creating the file `/var/run/qubes-vpn/debug` 192 | and restarting `qubes-vpn.service` while looking at the 193 | `journalctl -fab` output. 194 | 195 | File issues on this project if you could not get it to work, 196 | or there are errors in the software or the documentation. 197 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Eliminate requirement that VPN remote side set a default gateway. 2 | -------------------------------------------------------------------------------- /doc/Qubes VPN filtering rules.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rudd-O/qubes-vpn/843fe1f29509b1fd70b570981a0af95c0d169a26/doc/Qubes VPN filtering rules.dia -------------------------------------------------------------------------------- /doc/Qubes VPN filtering rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rudd-O/qubes-vpn/843fe1f29509b1fd70b570981a0af95c0d169a26/doc/Qubes VPN filtering rules.png -------------------------------------------------------------------------------- /doc/Qubes VPN.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rudd-O/qubes-vpn/843fe1f29509b1fd70b570981a0af95c0d169a26/doc/Qubes VPN.dia -------------------------------------------------------------------------------- /doc/Qubes VPN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rudd-O/qubes-vpn/843fe1f29509b1fd70b570981a0af95c0d169a26/doc/Qubes VPN.png -------------------------------------------------------------------------------- /qubes-vpn.spec: -------------------------------------------------------------------------------- 1 | %define debug_package %{nil} 2 | 3 | %define mybuildnumber %{?build_number}%{?!build_number:1} 4 | 5 | Name: qubes-vpn 6 | Version: 0.1.0 7 | Release: %{mybuildnumber}%{?dist} 8 | Summary: Leakproof VPN for your Qubes OS ProxyVMs 9 | BuildArch: noarch 10 | 11 | License: GPLv3+ 12 | URL: https://github.com/Rudd-O/qubes-vpn 13 | Source0: https://github.com/Rudd-O/%{name}/archive/{%version}.tar.gz#/%{name}-%{version}.tar.gz 14 | 15 | BuildRequires: make 16 | BuildRequires: sed 17 | Requires: openvpn 18 | Requires: iptables 19 | Requires: /sbin/ip 20 | Requires: /sbin/sysctl 21 | Requires: qubes-db 22 | Requires: util-linux 23 | Requires: ipcalc 24 | Requires: sudo 25 | Requires: coreutils 26 | Requires: libnotify 27 | Requires: gawk 28 | Requires: gtk-update-icon-cache 29 | Requires: desktop-file-utils 30 | 31 | %description 32 | This package lets you setup an OpenVPN-based leakproof VPN on Qubes OS. 33 | 34 | %prep 35 | %setup -q 36 | 37 | %build 38 | # variables must be kept in sync with install 39 | make DESTDIR=$RPM_BUILD_ROOT SBINDIR=%{_sbindir} BINDIR=%{_bindir} UNITDIR=%{_unitdir} PRESETDIR=%{_prefix}/lib/systemd/system-preset/ SYSCONFDIR=%{_sysconfdir} LIBEXECDIR=%{_libexecdir} 40 | 41 | %install 42 | rm -rf $RPM_BUILD_ROOT 43 | # variables must be kept in sync with build 44 | make install DESTDIR=$RPM_BUILD_ROOT SBINDIR=%{_sbindir} BINDIR=%{_bindir} UNITDIR=%{_unitdir} PRESETDIR=%{_prefix}/lib/systemd/system-preset/ SYSCONFDIR=%{_sysconfdir} DATADIR=%{_datadir} LIBEXECDIR=%{_libexecdir} 45 | 46 | %check 47 | if grep --exclude='*.png' -r '@.*@' -- $RPM_BUILD_ROOT ; then 48 | echo "Check failed: files with AT identifiers appeared" >&2 49 | exit 1 50 | fi 51 | 52 | %files 53 | %attr(0755, root, root) %{_sbindir}/qubes-vpn* 54 | %attr(0755, root, root) %{_bindir}/qubes-vpn* 55 | %attr(0755, root, root) %{_libexecdir}/qubes-vpn* 56 | %attr(0644, root, root) %{_unitdir}/qubes-vpn* 57 | %attr(0644, root, root) %{_prefix}/lib/systemd/system-preset/*qubes-vpn* 58 | %attr(0440, root, root) %{_sysconfdir}/sudoers.d/qubes-vpn 59 | %attr(0644, root, root) %{_sysconfdir}/xdg/autostart/qubes-vpn-notifier.desktop 60 | %attr(0644, root, root) %{_datadir}/icons/hicolor/48x48/apps/qubes-vpn.png 61 | %attr(0644, root, root) %{_datadir}/applications/qubes-vpn-configurator.desktop 62 | %doc README.md 63 | 64 | %pre 65 | getent group qubes-vpn >/dev/null || groupadd -r qubes-vpn || : 66 | getent passwd qubes-vpn >/dev/null || \ 67 | useradd -c "Privilege-separated Qubes VPN" -g qubes-vpn \ 68 | -s /sbin/nologin -r -d /var/empty/qubes-vpn qubes-vpn 2> /dev/null || : 69 | 70 | %post 71 | %systemd_post qubes-vpn.service qubes-vpn-forwarding.service qubes-vpn-configuration.path 72 | for unit in qubes-vpn.service qubes-vpn-forwarding.service qubes-vpn-configuration.path ; do 73 | if [ "$(systemctl is-enabled $unit 2>&1)" == "disabled" ] ; then 74 | systemctl --no-reload preset $unit 75 | fi 76 | done 77 | update-desktop-database >&/dev/null || : 78 | touch %{_datadir}/icons/hicolor >&/dev/null || : 79 | if [ $1 -eq 1 ]; then 80 | systemctl start qubes-vpn.service 81 | systemctl start qubes-vpn-forwarding.service 82 | systemctl start qubes-vpn-configuration.path 83 | fi 84 | 85 | %preun 86 | %systemd_preun qubes-vpn.service qubes-vpn-forwarding.service qubes-vpn-configuration.path 87 | 88 | %postun 89 | %systemd_postun_with_restart qubes-vpn.service qubes-vpn-forwarding.service qubes-vpn-configuration.path 90 | if [ $1 -eq 0 ]; then 91 | update-desktop-database >&/dev/null || : 92 | touch --no-create %{_datadir}/icons/hicolor >&/dev/null || : 93 | gtk-update-icon-cache %{_datadir}/icons/hicolor >&/dev/null || : 94 | fi 95 | 96 | %changelog 97 | * Mon Oct 24 2016 Manuel Amador (Rudd-O) 98 | - Added rudimentary GUI configuration system and notifications. 99 | 100 | * Wed Oct 12 2016 Manuel Amador (Rudd-O) 101 | - Initial release. 102 | -------------------------------------------------------------------------------- /src/etc/sudoers.d/qubes-vpn.in: -------------------------------------------------------------------------------- 1 | Defaults:qubes-vpn !requiretty 2 | qubes-vpn ALL = NOPASSWD:@SBINDIR@/qubes-vpn-interface-control 3 | -------------------------------------------------------------------------------- /src/etc/xdg/autostart/qubes-vpn-notifier.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Encoding=UTF-8 4 | Name=Qubes VPN notifications 5 | Exec=@LIBEXECDIR@/qubes-vpn-notifier 6 | Terminal=False 7 | Type=Application 8 | Categories= 9 | GenericName= 10 | X-GNOME-Autostart-Phase=Initialization 11 | OnlyShowIn=X-QUBES; 12 | -------------------------------------------------------------------------------- /src/usr/bin/qubes-vpn-configurator.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | conf="@VPNCONFDIR@/@VPNCONFFILE@" 4 | confdir="@VPNCONFDIR@" 5 | 6 | wrap() { 7 | if tty >/dev/null 2>&1 ; then 8 | "$@" 9 | else 10 | xterm -e "$@" 11 | fi 12 | } 13 | 14 | edit() { 15 | local tmpfile 16 | local bbefore 17 | local bafter 18 | 19 | bbefore=$(md5sum "$1" | cut -d " " -f 1) 20 | 21 | tmpfile=$(mktemp /tmp/"@VPNCONFFILE@".XXXXXXXX) 22 | cat "$1" > "$tmpfile" 23 | 24 | if [ -n "$VISUAL" ] ; then 25 | wrap "$VISUAL" "$tmpfile" 26 | elif [ -n "$EDITOR" ] ; then 27 | wrap "$EDITOR" "$tmpfile" 28 | elif type gedit >/dev/null 2>&1 ; then 29 | gedit "$tmpfile" 30 | elif type kwrite >/dev/null 2>&1 ; then 31 | kwrite "$tmpfile" 32 | elif type nano >/dev/null 2>&1 ; then 33 | wrap nano "$tmpfile" 34 | else 35 | wrap vi "$tmpfile" 36 | fi 37 | 38 | bafter=$(md5sum "$tmpfile" | cut -d " " -f 1) 39 | 40 | if [ "$bbefore" != "$bafter" ] ; then 41 | cat "$tmpfile" > "$1" 42 | fi 43 | 44 | rm -f "$tmpfile" 45 | } 46 | 47 | test -f "$conf" && { 48 | before=$(md5sum "$conf") 49 | edit "$conf" 50 | after=$(md5sum "$conf") 51 | } || { 52 | created= 53 | test -d "$confdir" || { 54 | created=true 55 | sudo mkdir -p "$confdir" 56 | sudo chown user "$confdir" 57 | } 58 | 59 | cat > "$conf.sample" << "EOF" 60 | # Qubes VPN configuration 61 | # ======================= 62 | # 63 | # Place your VPN configuration in this file. This progam will take care 64 | # of placing the configuration in the right location for you. 65 | # 66 | # If your OpenVPN configuration refers to other files, such as credentials, 67 | # add them to @VPNCONFDIR@. References in this configuration to those 68 | # additional files should be relative paths, since the OpenVPN daemon changes 69 | # its working directory to @VPNCONFDIR@, prior to starting up. 70 | # 71 | # Once you are done editing the configuration, save this file and 72 | # close the editor. The VPN service will start automatically. 73 | # 74 | # Problems? Refer to the README.md file included in this program for 75 | # troubleshooting instructions and issue reporting information. 76 | # 77 | # Ensure that your OpenVPN server sends a default gateway. This gateway 78 | # will be automatically used as the default route for the VMs that use 79 | # this VPN VM as NetVM. 80 | # 81 | # Here is a sample configuration file. Note how it refers to a file 82 | # qubes-vpn.creds that must be created by you in the same directory. 83 | # 84 | # ========= Start sample file ========= 85 | #client 86 | #dev tun0 87 | #proto udp 88 | # 89 | # # host and port 90 | #remote mullvad.net 1194 91 | #resolv-retry infinite 92 | #nobind 93 | # 94 | # # username and password stored in file qubes-vpn.creds 95 | #auth-user-pass qubes-vpn.creds 96 | #auth-retry nointeract 97 | # 98 | #ca [inline] 99 | # 100 | #tls-client 101 | #tls-auth [inline] 102 | #ns-cert-type server 103 | # 104 | #keepalive 10 30 105 | #cipher AES-256-CBC 106 | #persist-key 107 | #persist-tun 108 | #comp-lzo 109 | #tun-mtu 1500 110 | #mssfix 1200 111 | #passtos 112 | #verb 3 113 | # 114 | # 115 | #-----BEGIN CERTIFICATE----- 116 | #... 117 | #-----END CERTIFICATE----- 118 | # 119 | # 120 | # 121 | #-----BEGIN OpenVPN Static key V1----- 122 | #... 123 | #-----END OpenVPN Static key V1----- 124 | # 125 | # ========== End sample file ========== 126 | 127 | EOF 128 | before=$(md5sum "$conf".sample) 129 | edit "$conf".sample 130 | after=$(md5sum "$conf".sample) 131 | if [ "$before" != "$after" ] ; then 132 | mv -f "$conf".sample "$conf" 133 | else 134 | if [ -n "$created" ] ; then 135 | sudo rm -rf "$confdir" 136 | fi 137 | fi 138 | } 139 | -------------------------------------------------------------------------------- /src/usr/lib/systemd/system-preset/78-qubes-vpn.preset: -------------------------------------------------------------------------------- 1 | enable qubes-vpn.service 2 | enable qubes-vpn-forwarding.service 3 | enable qubes-vpn-configuration.path 4 | -------------------------------------------------------------------------------- /src/usr/lib/systemd/system/qubes-vpn-configuration.path.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Qubes OS leakproof VPN configuration monitor 3 | ConditionPathExists=@QUBESSERVICEDIR@/qubes-vpn 4 | 5 | [Path] 6 | PathChanged=@VPNCONFDIR@/@VPNCONFFILE@ 7 | PathChanged=@VPNCONFDIR@ 8 | 9 | [Install] 10 | WantedBy=network.target 11 | -------------------------------------------------------------------------------- /src/usr/lib/systemd/system/qubes-vpn-configuration.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Qubes OS leakproof VPN configuration restarter 3 | ConditionPathExists=@QUBESSERVICEDIR@/qubes-vpn 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=@LIBEXECDIR@/qubes-vpn-config-change-detector 8 | -------------------------------------------------------------------------------- /src/usr/lib/systemd/system/qubes-vpn-forwarding.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Forbid VM traffic until Qubes OS leakproof VPN is up 3 | ConditionPathExists=@QUBESSERVICEDIR@/qubes-vpn 4 | Before=qubes-network.service 5 | After=qubes-iptables.service 6 | PartOf=qubes-iptables.service 7 | 8 | [Service] 9 | PrivateTmp=true 10 | Type=oneshot 11 | RemainAfterExit=true 12 | ExecStart=@SBINDIR@/qubes-vpn-forwarding on 13 | ExecStop=@SBINDIR@/qubes-vpn-forwarding off 14 | 15 | [Install] 16 | WantedBy=network.target 17 | -------------------------------------------------------------------------------- /src/usr/lib/systemd/system/qubes-vpn.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Qubes OS leakproof VPN 3 | ConditionPathExists=@VPNCONFDIR@/@VPNCONFFILE@ 4 | ConditionPathExists=@QUBESSERVICEDIR@/qubes-vpn 5 | After=network.target network.target qubes-network.service qubes-vpn-forwarding.service 6 | 7 | [Service] 8 | PrivateTmp=true 9 | Type=forking 10 | PIDFile=@VPNRUNDIR@/pid 11 | ExecStartPre=@BINDIR@/mkdir -p @VPNRUNDIR@ 12 | ExecStartPre=@BINDIR@/chown -R qubes-vpn @VPNRUNDIR@ 13 | ExecStartPre=@BINDIR@/chgrp -R qubes-vpn @VPNCONFDIR@ 14 | ExecStartPre=@BINDIR@/chmod -R g+rX @VPNCONFDIR@ 15 | ExecStart=@SBINDIR@/openvpn --daemon --writepid @VPNRUNDIR@/pid --cd @VPNCONFDIR@ --config @VPNCONFFILE@ --route-noexec --script-security 2 --up @SBINDIR@/qubes-vpn-interface-control --down @SBINDIR@/qubes-vpn-interface-control --up-restart --user qubes-vpn --group qubes-vpn --ifconfig-noexec --persist-tun --mlock 16 | ExecStartPost=@BINDIR@/chown qubes-vpn @VPNRUNDIR@/pid 17 | Restart=on-failure 18 | 19 | [Install] 20 | WantedBy=network.target 21 | -------------------------------------------------------------------------------- /src/usr/libexec/qubes-vpn-config-change-detector.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | currstate=$(md5sum "@VPNCONFDIR@"/* 2>/dev/null || true | grep -v '.sample' | sort) 4 | prevstate=$(cat "@VPNRUNDIR@"/config-state 2>/dev/null || true) 5 | echo "$currstate" > "@VPNRUNDIR@"/config-state 2>/dev/null || true 6 | 7 | if [ "$currstate" != "$prevstate" ] ; then 8 | echo "Qubes VPN configuration changed, restarting VPN in 1 second" >&2 9 | sleep 1 10 | systemctl --no-block restart qubes-vpn.service 11 | fi 12 | -------------------------------------------------------------------------------- /src/usr/libexec/qubes-vpn-notifier.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | conf="@VPNCONFDIR@/@VPNCONFFILE@" 4 | serviceflag="@QUBESSERVICEDIR@"/qubes-vpn 5 | 6 | msg() { 7 | local title=$( echo "$1" | awk -F : ' { print $1 } ' ) 8 | local subtitle=$( echo "$1" | awk -F : ' { gsub(/^ /, "", $2); print $2 } ' ) 9 | notify-send --app-name=qubes-vpn --expire-time=10000 "$title" "$subtitle" 10 | } 11 | 12 | test -e "$serviceflag" || exit 13 | if ! test -f "$conf" ; then 14 | msg "Qubes VPN is ready to be configured: Run the qubes-vpn-configurator program on your VPN VM to get started." 15 | fi 16 | 17 | state=unknown 18 | 19 | while read -r line; do 20 | if echo "$line" | egrep -q '^Qubes VPN on (-|[a-z0-9_.])+ connected: ' ; then 21 | if [ $state != connected ] ; then 22 | state=connected 23 | msg "$line" 24 | fi 25 | elif echo "$line" | egrep -q '^Qubes VPN on (-|[a-z0-9_.])+ disconnected: ' ; then 26 | if [ $state != disconnected ] ; then 27 | state=disconnected 28 | msg "$line" 29 | fi 30 | fi 31 | done < <(tail -c+0 -F "@VPNRUNDIR@"/events) 32 | -------------------------------------------------------------------------------- /src/usr/sbin/qubes-vpn-forwarding.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ -e "@VPNRUNDIR@"/debug ] ; then 4 | set -x 5 | fi 6 | 7 | if [ "$1" == "on" ] ; then 8 | @SBINDIR@/ip route add table 78 blackhole default metric 1000 9 | @SBINDIR@/ip rule add fwmark 0x78 table 78 10 | @SBINDIR@/iptables -t mangle -A PREROUTING -j MARK --set-mark 0x78 -i vif+ 11 | @SBINDIR@/iptables -t nat -L QUBES-VPN >& /dev/null || { 12 | @SBINDIR@/iptables -t nat -N QUBES-VPN 13 | } 14 | @SBINDIR@/iptables-save | grep -q -- '-j QUBES-VPN' || { 15 | @SBINDIR@/iptables -t nat -I PREROUTING 1 -m mark --mark 0x78 -j QUBES-VPN 16 | } 17 | elif [ "$1" == "off" ] ; then 18 | @SBINDIR@/iptables -t mangle -F PREROUTING 19 | @SBINDIR@/ip rule del fwmark 0x78 20 | @SBINDIR@/ip route flush table 78 21 | elif [ "$1" == "blackhole" ] ; then 22 | while true ; do 23 | if [ "$( @SBINDIR@/ip route list table 78 | head -1 | grep -v ^blackhole || true )" == "" ] ; then 24 | break 25 | fi 26 | @SBINDIR@/ip route del table 78 $( @SBINDIR@/ip route list table 78 | head -1 | grep -v ^blackhole || true ) 27 | done 28 | elif [ "$1" == "setuprouting" ] ; then 29 | subnet=$2 30 | gateway=$3 31 | device=$4 32 | @SBINDIR@/ip route replace table 78 to "$subnet" dev "$device" 33 | @SBINDIR@/ip route replace table 78 to default via "$gateway" dev "$device" 34 | @SBINDIR@/ip route del to "$subnet" dev "$device" 35 | @SBINDIR@/sysctl -w net.ipv4.conf."$device".rp_filter=2 36 | fi 37 | -------------------------------------------------------------------------------- /src/usr/sbin/qubes-vpn-interface-control.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ -e "@VPNRUNDIR@"/debug ] ; then 4 | set -x 5 | fi 6 | 7 | myname=$( basename "$0" ) 8 | 9 | if [ "$UID" != "0" ] ; then 10 | @BINDIR@/env | sudo -u root -H -- "$0" "$@" 11 | exit $? 12 | fi 13 | 14 | exec 1> >(logger -s -t "$myname") 2>&1 15 | 16 | # Now we securely read the variables from standard input. 17 | # We only accept those variables we care about. 18 | while IFS== read -r v V ; do 19 | process= 20 | if [ "$v" == "script_type" ] ; then process=1 ; fi 21 | if [ "$v" == "ifconfig_local" ] ; then process=1 ; fi 22 | if [ "$v" == "ifconfig_remote" ] ; then process=1 ; fi 23 | if [ "$v" == "ifconfig_netmask" ] ; then process=1 ; fi 24 | if [ "$v" == "ifconfig_broadcast" ] ; then process=1 ; fi 25 | if [ "$v" == "route_vpn_gateway" ] ; then process=1 ; fi 26 | if [ "$v" == "link_mtu" ] ; then process=1 ; fi 27 | if [[ $v == foreign_option_* ]] ; then process=1 ; fi 28 | 29 | if [ "$process" == "1" ] ; then 30 | export "$v"="$V" 31 | fi 32 | done 33 | 34 | initorrestart="$6" 35 | device="$1" 36 | 37 | if [ "$script_type" == "up" ] ; then 38 | @SBINDIR@/qubes-vpn-forwarding blackhole 39 | 40 | # Set up DNS. 41 | @SBINDIR@/iptables -t nat -F QUBES-VPN 42 | primary_dns_vpn= 43 | secondary_dns_vpn= 44 | dnsstep=primary 45 | for n in `seq 10000` ; do 46 | if [ -n "$(eval echo \$foreign_option_$n)" ] ; then 47 | dns=$(eval echo \$foreign_option_$n | grep DNS | awk ' { print $3 } ') 48 | if [ -n "$dns" ] ; then 49 | if [ "$dnsstep" == "primary" ] ; then 50 | primary_dns=`@BINDIR@/qubesdb-read /qubes-netvm-gateway` 51 | echo "Forwarding primary DNS $primary_dns to $dns" >&2 52 | @SBINDIR@/iptables -t nat -A QUBES-VPN -p udp -m udp --dport 53 -d "$primary_dns" -j DNAT --to-destination "$dns" 53 | @SBINDIR@/iptables -t nat -A QUBES-VPN -p tcp -m tcp --dport 53 -d "$primary_dns" -j DNAT --to-destination "$dns" 54 | primary_dns_vpn="$dns" 55 | dnsstep=secondary 56 | elif [ "$dnsstep" == "secondary" ] ; then 57 | secondary_dns=`@BINDIR@/qubesdb-read /qubes-netvm-secondary-dns` 58 | echo "Forwarding secondary DNS $secondary_dns to $dns" >&2 59 | @SBINDIR@/iptables -t nat -A QUBES-VPN -p udp -m udp --dport 53 -d "$secondary_dns" -j DNAT --to-destination "$dns" 60 | @SBINDIR@/iptables -t nat -A QUBES-VPN -p tcp -m tcp --dport 53 -d "$secondary_dns" -j DNAT --to-destination "$dns" 61 | secondary_dns_vpn="$dns" 62 | dnsstep=nomoarnomoar 63 | else 64 | true 65 | fi 66 | fi 67 | else 68 | break 69 | fi 70 | done 71 | # Catch DNS requests when the remote side did not send us two DNS servers. 72 | if [ "$dnsstep" == "secondary" -a -n "$dns" ] ; then 73 | secondary_dns=`@BINDIR@/qubesdb-read /qubes-netvm-secondary-dns` 74 | echo "Forwarding secondary DNS $secondary_dns to $dns" >&2 75 | @SBINDIR@/iptables -t nat -A QUBES-VPN -p udp -m udp --dport 53 -d "$secondary_dns" -j DNAT --to-destination "$dns" 76 | @SBINDIR@/iptables -t nat -A QUBES-VPN -p tcp -m tcp --dport 53 -d "$secondary_dns" -j DNAT --to-destination "$dns" 77 | secondary_dns_vpn="$dns" 78 | dnsstep=nomoarnomoar 79 | fi 80 | 81 | echo "Starting Qubes VPN initialization" >&2 82 | if [ "$initorrestart" == "init" ] ; then 83 | if [ -z "$ifconfig_local" ] ; then 84 | logger -t "$myname" -p error "OpenVPN did not pass us a valid ifconfig_local variable" 85 | env | sort >&2 86 | elif [ -z "$link_mtu" ] ; then 87 | logger -t "$myname" -p error "OpenVPN did not pass us a valid link_mtu variable" 88 | env | sort >&2 89 | elif [ -z "$route_vpn_gateway" ] ; then 90 | logger -t "$myname" -p error "OpenVPN did not pass us a valid route_vpn_gateway variable" 91 | env | sort >&2 92 | else 93 | if [ -n "$ifconfig_remote" ] ; then 94 | echo "Setting up $dev_type device $device with MTU $link_mtu" >&2 95 | @SBINDIR@/ip link set dev "$device" up mtu "$link_mtu" 96 | echo "Setting up address $ifconfig_local with peer $ifconfig_remote" >&2 97 | @SBINDIR@/ip addr add dev "$device" "$ifconfig_local" peer "$ifconfig_remote" 98 | echo "Adding default route $route_vpn_gateway and peer route $ifconfig_remote in $device to VPN routing table" >&2 99 | @SBINDIR@/qubes-vpn-forwarding setuprouting "$ifconfig_remote"/255.255.255.255 "$route_vpn_gateway" "$device" 100 | else 101 | if [ -z "$ifconfig_netmask" -o ] ; then 102 | logger -t "$myname" -p error "OpenVPN did not pass us a valid ifconfig_netmask variable" 103 | env | sort >&2 104 | elif [ -z "$ifconfig_broadcast" ] ; then 105 | logger -t "$myname" -p error "OpenVPN did not pass us a valid ifconfig_broadcast variable" 106 | env | sort >&2 107 | fi 108 | eval $(@BINDIR@/ipcalc -4n "$ifconfig_local" "$ifconfig_netmask") ; ifconfig_network="$NETWORK" ; unset NETWORK 109 | echo "Setting up $dev_type device $device with MTU $link_mtu" >&2 110 | @SBINDIR@/ip link set dev "$device" up mtu "$link_mtu" 111 | echo "Setting up address $ifconfig_local with netmask $ifconfig_netmask" >&2 112 | @SBINDIR@/ip addr add dev "$device" "$ifconfig_local"/"$ifconfig_netmask" broadcast "$ifconfig_broadcast" 113 | echo "Adding default route $route_vpn_gateway and subnet route $ifconfig_network/$ifconfig_netmask in $device to VPN routing table" >&2 114 | @SBINDIR@/qubes-vpn-forwarding setuprouting "$ifconfig_network"/"$ifconfig_netmask" "$route_vpn_gateway" "$device" 115 | fi 116 | for n in `seq 10000` ; do 117 | if [ -n "$(eval echo \$route_network_$n)" ] ; then 118 | net=$(eval echo \$route_network_$n ) 119 | mask=$(eval echo \$route_netmask_$n ) 120 | gateway=$(eval echo \$route_gateway_$n ) 121 | metric=$(eval echo \$route_metric_$n ) 122 | echo "Server sent us additional routes: $net/$mask gw $gateway metric $metric" >&2 123 | else 124 | break 125 | fi 126 | done 127 | msg="Traffic flows through $route_vpn_gateway." 128 | if [ -n "$primary_dns" ] ; then 129 | msg="$msg\nDNS requests go to $primary_dns_vpn" 130 | if [ -n "$secondary_dns" ] ; then 131 | msg="$msg and $secondary_dns_vpn" 132 | fi 133 | msg="$msg." 134 | fi 135 | echo "Qubes VPN on $HOSTNAME connected: $msg" >> "@VPNRUNDIR@"/events 136 | fi 137 | fi 138 | echo "Finished Qubes VPN initialization" >&2 139 | elif [ "$script_type" == "down" ] ; then 140 | echo "Starting Qubes VPN turndown" >&2 141 | echo "Blackholing the VPN routing table" >&2 142 | @SBINDIR@/qubes-vpn-forwarding blackhole 143 | echo "Qubes VPN on $HOSTNAME disconnected: All traffic has been blackholed." >> "@VPNRUNDIR@"/events 144 | if [ "$initorrestart" == "init" ] ; then 145 | if [ -z "$ifconfig_local" ] ; then 146 | logger -t "$myname" -p error "OpenVPN did not pass us a valid ifconfig_local variable" 147 | env | sort >&2 148 | elif [ -z "$ifconfig_netmask" ] ; then 149 | logger -t "$myname" -p error "OpenVPN did not pass us a valid ifconfig_netmask variable" 150 | env | sort >&2 151 | else 152 | @SBINDIR@/ip link show dev "$device" >/dev/null 2>&1 && { 153 | @SBINDIR@/ip addr del dev "$device" "$ifconfig_local"/"$ifconfig_netmask" 154 | } || { true ; } 155 | fi 156 | fi 157 | echo "Finished Qubes VPN turndown" >&2 158 | fi 159 | -------------------------------------------------------------------------------- /src/usr/share/applications/qubes-vpn-configurator.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Qubes VPN configurator 3 | Comment=Configure your Qubes VPN 4 | TryExec=@BINDIR@/qubes-vpn-configurator 5 | Exec=@BINDIR@/qubes-vpn-configurator 6 | Icon=qubes-vpn 7 | StartupNotify=false 8 | Terminal=false 9 | Type=Application 10 | Categories=Settings; 11 | Keywords=VPN;Network; 12 | -------------------------------------------------------------------------------- /src/usr/share/icons/hicolor/48x48/apps/qubes-vpn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rudd-O/qubes-vpn/843fe1f29509b1fd70b570981a0af95c0d169a26/src/usr/share/icons/hicolor/48x48/apps/qubes-vpn.png --------------------------------------------------------------------------------