Fedora 40 Minimal(OpenVPN) - Qubes 4.2 - Six Easy Steps

You can use the bash script in the end of this tutorial to automate the installation.

1. INSTALL TEMPLATE (fedora-40-minimal) AND UPDATE IT

Open dom0 terminal and type:

sudo qubes-dom0-update qubes-template-fedora-40-minimal && sudo qvm-run -u root --pass-io fedora-40-minimal “xterm -e ‘dnf update && dnf upgrade && dnf autoremove && read’” && qvm-shutdown fedora-40-minimal

2. CLONE TEMPLATE & CREATE VPN TEMPLATE

dom0:

qvm-clone fedora-40-minimal f40m-vpn && qvm-run -u root --pass-io f40m-vpn “xterm -e ‘dnf install qubes-input-proxy-sender qubes-core-agent-passwordless-root qubes-core-agent-networking qubes-core-agent-dom0-updates qubes-core-agent-network-manager NetworkManager-wifi network-manager-applet notification-daemon thunar qubes-core-agent-thunar qubes-menus pciutils iputils less psmisc gnome-keyring chromium dbus-x11 dejavu-sans-fonts tinyproxy notification-daemon xfce4-terminal nethogs geany openvpn openssl && read’”

3. FIXING freedesktop.notifications.service VPN TEMPLATE

dom0:

qvm-run -u root f40m-vpn ‘geany /usr/share/dbus-1/services/org.freedesktop.Notifications.service’

when geany prompts copy and paste the text below then save the file and shutdown the template.

[D-BUS Service]
Name=org.freedesktop.Notifications
Exec=/usr/libexec/notification-daemon

4. CREATE APPVM-VPN

qvm-create --template f40m-vpn --class AppVM --label red default-f40m-vpn && qvm-prefs default-f40m-vpn provides_network on

5. SETUP FILES/SCRIPTS INSIDE DEFAULT-APPVM-VPN
5.1 Create vpn directory:

qvm-run -u root --pass-io --nogui default-f40m-vpn ‘mkdir /rw/config/vpn’

5.2 Edit /rw/config/qubes-firewall-user-script

qvm-run -u root default-f40m-vpn ‘geany /rw/config/qubes-firewall-user-script’

copy and paste this:

#!/bin/bash

#    Block forwarding of connections through upstream network device
#    (in case the vpn tunnel breaks):
# Prevent the qube to forward traffic outside of the VPN

nft insert rule qubes custom-forward oifname eth0 counter drop
nft insert rule ip6 qubes custom-forward oifname eth0 counter drop
nft insert rule qubes custom-forward iifname eth0 counter drop
nft insert rule ip6 qubes custom-forward iifname eth0 counter drop

#    Add the `vpntunnel` group to system, if it doesn't already exist
if ! grep -q "^vpntunnel:" /etc/group ; then
     sleep 3s
     groupadd -rf vpntunnel
     sync
fi
sleep 2s

#    Accept traffic to VPN
nft 'add chain qubes output { type filter hook output priority 0; policy accept; }'

#    Allow traffic from the `vpntunnel` group to the uplink interface (eth0);
#    Our VPN client will run with group `vpntunnel`.
nft insert rule ip qubes output oifname eth0 skgid vpntunnel accept

While using geany app create a new file named qubes-vpn-status.sh and save it in /rw/config/vpn/ folder with this content:

#!/bin/bash
set -e
export PATH="$PATH:/usr/sbin:/sbin"

case "$1" in

up)
# To override DHCP DNS, assign DNS addresses to 'vpn_dns' env variable before calling this script;
# Format is 'X.X.X.X  Y.Y.Y.Y [...]'
if [[ -z "$vpn_dns" ]] ; then
    # Parses DHCP foreign_option_* vars to automatically set DNS address translation:
    for optionname in ${!foreign_option_*} ; do
        option="${!optionname}"
        unset fops; fops=($option)
        if [ ${fops[1]} == "DNS" ] ; then vpn_dns="$vpn_dns ${fops[2]}" ; fi
    done
fi


nft flush chain ip qubes dnat-dns

if [[ -n "$vpn_dns" ]] ; then
    # Set DNS address translation in firewall:
    for addr in $vpn_dns; do
        nft add rule qubes dnat-dns iifname == "vif*" tcp dport 53 dnat "$addr"
        nft add rule qubes dnat-dns iifname == "vif*" udp dport 53 dnat "$addr"
    done
    su - -c 'notify-send "$(hostname): LINK IS UP." --icon=network-server' user
else
    su - -c 'notify-send "$(hostname): LINK UP, NO DNS!" --icon=dialog-error' user
fi

;;
down)
su - -c 'notify-send "$(hostname): LINK IS DOWN !" --icon=dialog-error' user

# Restart the VPN automatically
sleep 5s
sudo /rw/config/rc.local
;;
esac

5.3. CHANGES IN /rw/config/rc.local

using geany open the file /rw/config/rc.local

copy and paste this content and then save it:

#!/bin/bash
VPN_CLIENT='openvpn'
VPN_OPTIONS='--cd /rw/config/vpn/ --config openvpn-client.ovpn --daemon'

# vpntunnel
sudo sg vpntunnel -c "$VPN_CLIENT $VPN_OPTIONS"

5.4 Make some files with chmod and shutdown default-appvm-vpn

dom0:

qvm-run -u root --pass-io --nogui default-f40m-vpn ‘chmod +x /rw/config/vpn/qubes-vpn-status.sh’ && qvm-run -u root --pass-io --nogui default-f40m-vpn ‘chmod +x /rw/config/rc.local’ && qvm-run -u root --pass-io --nogui default-f40m-vpn ‘chmod +x /rw/config/qubes-firewall-user-script’ && qvm-shutdown default-f40m-vpn

Now you have created a default vpn template and appvm, from this one you can clone and make many appvms-vpns you want.

6. Clone the default-f40m-vpn to use with your config files…

dom0:

qvm-clone default-f40m-vpn sys-vpn-test

Start sys-vpn-test copy all your vpn config files into the folder /rw/config/vpn/

Edit your .ovpn file and inside it add the lines

ATTENTION: rename your .ovpn file to openvpn-client.ovpn

script-security 2
up 'qubes-vpn-status up'
down 'qubes-vpn-status down'

Save it in /rw/config/vpn/ renamed to: openvpn-client.ovpn

the folder /rw/config/vpn/ inside sys-vpn-test, in this example the keys/certs was gen separated, it will look like this:

rw folder

Restart your sys-vpn-test and if you have done everything right you’ll be able to use it, run a dispvm, change the net-qube to sys-vpn-test browser to ipleak.net and test it, while running the test you can check in vpn terminal the connection using: sudo nethogs.

BASH SCRIPT TO AUTOMATE THE INSTALLATION:

This script will execute the same commands above, you can copy this code and save it inside any vm you want then copy it to dom0 and execute it. For example:

i saved the file in dispvm: disp1300
file named: vpnbash

Open dom0 terminal and type:

qvm-run --pass-io --nogui disp1300 ‘cat /home/user/vpnbash’ >> vpnbash.sh

after you save the script in dom0 run:

sudo bash vpnbash.sh

the bash script will popup 3 terminals running nano during installation, each one for a file:

qubes-firewall-user-script
qubes-vpn-status.sh
rc.local

copy and paste the codes on each file, after saving it, close terminal to continue the installation.

BASH SCRIPT:

#!/bin/bash

# Templates and AppVM Names
min_template="fedora-40-minimal"
min_vpn_template="f40m-vpn"
default_min_vpn_vm="default-f40m-vpn"


# Template name for instalation
min_template="fedora-40-minimal"

# Template Verification
function template_exists {
    qvm-ls | grep -w "$min_template" &> /dev/null
    return $?  
}

# Verify if the minimal template exists
if template_exists; then
    echo "$min_template is alread installed. Skip this action."
else
    # Install the minimal template
    echo "Downloading and installing $min_template..."
    sudo qubes-dom0-update $min_template

    # Verify the intallation
    if [ $? -ne 0 ]; then
        echo "Fail to install the $min_template. Aborting."
        exit 1
    fi
fi

# Run the update/upgrade in installed template
echo "Updating/upgrading $min_template..."
sudo qvm-run -u root --pass-io $min_template "xterm -e 'dnf update && dnf upgrade && dnf autoremove && read -p \"Finished. Press enter to continue ...\"'"

# Verify the update
if [ $? -ne 0 ]; then
    echo "Fail to update/upgrade $min_template"
    exit 1
fi

# Shutdown template
echo "Shutting down $min_template..."
qvm-shutdown $min_template

# Checking powering off template
if [ $? -eq 0 ]; then
    echo "$min_template powered off."
else
    echo "Fail to shutting down the template."
fi

# Check if the min vpn already exists before cloning it
function template_exists2 {
    qvm-ls | grep -w "$min_vpn_template" &> /dev/null
    return $?
}

# check clone template
if template_exists2; then
    echo "The $min_vpn_template already exists. Skip clone action."
else
    # Cloning the template
    echo "Cloning $min_template to $min_vpn_template..."
    qvm-clone $min_template $min_vpn_template

    # Check the clone
    if [ $? -ne 0 ]; then
        echo "Fail to clone $min_template to $min_vpn_template. Aborting...."
        exit 1
    fi
fi

# Powering off the main template
echo "Shutting down $min_template..."
qvm-shutdown $min_template

# Installing main packages in vpn template
echo "Installing packages in $min_vpn_template..."
qvm-start $min_vpn_template && qvm-run -u root --pass-io $min_vpn_template "xterm -e 'dnf -y install qubes-input-proxy-sender qubes-core-agent-passwordless-root qubes-core-agent-networking qubes-core-agent-dom0-updates qubes-core-agent-network-manager NetworkManager-wifi network-manager-applet notification-daemon thunar qubes-core-agent-thunar qubes-menus pciutils iputils less psmisc gnome-keyring chromium dbus-x11 dejavu-sans-fonts tinyproxy notification-daemon xfce4-terminal nethogs geany openvpn openssl && read -p \"Finished.. Press Enter to continue...\"'"

# Check Install
if [ $? -ne 0 ]; then
    echo "Fail to install packages on $min_vpn_template."
    exit 1
fi

# Fixing dbs services inside vpn template
echo "Fixing org.freedesktop.Notifications.service in $min_vpn_template..."
qvm-run -u root $min_vpn_template "tee /usr/share/dbus-1/services/org.freedesktop.Notifications.service > /dev/null" <<< '[D-BUS Service]
   Name=org.freedesktop.Notifications
   Exec=/usr/libexec/notification-daemon'

# Check fixing
if [ $? -ne 0 ]; then
    echo "Fail fixing org.freedesktop.Notifications.service."
    exit 1
fi

# Shutdown cloned template
echo "Shutting down $min_vpn_template..."
qvm-shutdown $min_vpn_template

# Check AppVM-VPN function
function appvm_exists {
    qvm-ls | grep -w "$default_min_vpn_vm" &> /dev/null
    return $?
}

# Verify if AppVM already exists
if appvm_exists; then
    echo "VM $default_min_vpn_vm already existe. Skip."
else
    # Create VM based on vpn template
    echo "Creating VM $default_min_vpn_vm..."
    qvm-create --template f40m-vpn --class AppVM --label red $default_min_vpn_vm

    # Verify action
    if [ $? -ne 0 ]; then
        echo "Fail to create $default_min_vpn."
        exit 1
    fi
fi

# Enable provides network on VM
echo "Activating network on $default_min_vpn_vm"
qvm-prefs $default_min_vpn_vm provides_network on

# Start VM
echo "Starting Default AppVM-VPN"
qvm-start $default_min_vpn_vm

# Read the configuration files then edit /rw/config/qubes-firewall-user-script
echo "Reading qubes-firewall-user-script for modifications"
qvm-run -u root $default_min_vpn_vm "xterm -e 'cat /rw/config/qubes-firewall-user-script && read -p \"Press enter to copy the code in qubes-firewall-user-script\" && nano /rw/config/qubes-firewall-user-script'"

qvm-run -u root $default_min_vpn_vm "xterm -e 'mkdir -p /rw/config/vpn && touch /rw/config/qubes-vpn-status.sh && read -p \"Press enter to copy the code in qubes-vpn-status.sh\" && nano /rw/config/qubes-vpn-status.sh'"

qvm-run -u root $default_min_vpn_vm "xterm -e 'cat /rw/config/rc.local && read -p \"Press enter to edit your rc.local\" && nano /rw/rc.local'"

qvm-run -u root $default_min_vpn_vm "xterm 'chmod +x /rw/config/qubes-firewall-user-script && chmod +x /rw/config/vpn/qubes-vpn-status.sh && chmod +x /rw/config/rc.local'"

echo ""
echo ""
echo ""
echo "Done, now copy and past your vpn files inside /rw/config/vpn/"
echo ""
echo "-> Rename your ovpn file to openvpn-client.ovpn"
echo "-> Edit your openvpn-client.ovpn file and add the script-security lines:"
echo ""
echo ""
echo "==============================="
echo ""
echo "script-security 2"
echo "up 'qubes-vpn-status up'"
echo "down 'qubes-vpn-status down'"
echo ""
echo "==============================="
echo ""
echo ""
echo "this bash script was created based qubes forum topic:"
echo "https://forum.qubes-os.org/t/fedora-40-minimal-openvpn-qubes-4-2-six-easy-steps"

3 Likes

Probably gotta check, why I didnt get the whole thing running, by using the bash script/s. However I did follow those 6 steps and it works like a charm.
Also a nice introduction to get dedicated network-feature cubes up and running.
Thanks!