Disposable sys-net: Automatically connect wifi (config file or RPC service)

Qubes OS - Disposable sys-net - Automatically connect wifi


0 - Description


This guide aims to answer to the (reccuring?) question:
How can I make my wifi connection remember my password?

In the “Configuration file” main section, you have 3 methods.

In the “RPC service” main section, you have an example of how to store your wifi credentials outside of the disposable template.

1 - Configuration file


1.1 - Copy the config file

Connect to your wifi with the Network-Manager panel icon.

Once connected, open the Network-Manager setting (right cick on the panel icon) and edit your connection:
Network-Manager > Edit Connections > Wi-FI: YOUR_SSID > Wi-Fi tab

In the Device field you might have wls6 or wls7 value (or else).
Delete it and leave the field empty.
Optionaly, instead of empty value, you could use your MAC address only (e.g. 3C:97:0E:42:1A:19).
Save and close the Network-Manager setting.

In your sys-net disposable:
Copy your config file to your dispobable template:

[user@sys-net-dvm ~]$ qvm-copy /etc/NetworkManager/system-connections/MY_SSID.nmconnection

Replace MY_SSID with your wireless SSID name.
Choose your sys-net disposable template in the popup prompt.

Shutdown your sys-net disposable.

In your sys-net disposable template:
Create NM-system-connections directory:

[user@sys-net-dvm-template ~]$ sudo mkdir -p /rw/config/NM-system-connections/

Copy your config file into it:

[user@sys-net-dvm-template ~]$ sudo cp /home/user/QubesIncoming/sys-net-dvm/MY_SSID.nmconnection /rw/config/NM-system-connections/

Delete QubesIncoming directory:

[user@sys-net-dvm-template ~]$ rm -r /home/user/QubesIncoming

Shutdown your sys-net disposable template.
Start your sys-net disposable.

1.2 - Create the config file

In your sys-net disposable template:
Create the following script (e.g /home/user/auto_connect_wifi.sh).

#!/usr/bin/bash

wifi_dir=/rw/config/NM-system-connections/
wifi_cfg=$wifi_dir/home.nmconnection

sudo mkdir -p $wifi_dir
echo '
[wifi]
ssid=MY_SSID_NAME

[wifi-security]
key-mgmt=wpa-psk
psk=MY_PASSWORD' | sudo tee $wifi_cfg > /dev/null
sudo chmod 600 $wifi_cfg

These 3 fields (ssid, key-mgmt and psk) are the minimum required.
The non-defined fields will use default values.

Replace home.nmconnection with your SSID or any meaningful name (e.g. SSID.nmconnection).
Replace MY_SSID_NAME and MY_PASSWORD with your values.

Make your script executable and launch it.

[user@sys-net-dvm-template ~]$ chmod +x auto_connect_wifi.sh
[user@sys-net-dvm-template ~]$ ./auto_connect_wifi.sh
[user@sys-net-dvm-template ~]$ rm auto_connect_wifi.sh

Shutdown your sys-net disposable template.
Start your sys-net disposable.

1.3 - Connect in the disposable template

This method is not recommended.
Because it require to connect your disposable template to the network.
And because there are other methods available.

Shutdown your sys-net disposable.

Change the setting of your sys-net disposable template:
Set the virtualization mode to hvm:

[user@dom0 ~]$ qvm-prefs sys-net-dvm-template virt_mode hvm

Attach your wireless controller:
wifi_devid is your wireless network id as shown by qvm-pci (e.g. dom0:00_42.0).

[user@dom0 ~]$ wifi_devid=$(qvm-pci | grep Network | cut -d ' ' -f 1)
[user@dom0 ~]$ qvm-pci attach sys-net-dvm-template $wifi_devid

Enable the Network-Manager service:
netvm must be set to none (it should already be the case).

[user@dom0 ~]$ qvm-prefs sys-net-dvm-template provides_network true

Start your sys-net disposable template.

Connect to your wifi with the Network-Manager panel icon.
In your connection setting:
Delete the Device field value and leave the field empty.

Shutdown your sys-net disposable template.

Disable the Network-Manager service:

[user@dom0 ~]$ qvm-prefs sys-net-dvm-template provides_network false

Detach your wireless controller:

[user@dom0 ~]$ qvm-pci detach sys-net-dvm-template $wifi_devid

Set back the virtualization mode to pvh:

[user@dom0 ~]$ qvm-prefs sys-net-dvm-template virt_mode pvh

Start your sys-net disposable.

2 - RPC service


Somehow mandatory reading to understand this part of the guide.
https://www.qubes-os.org/doc/qrexec/

Brief summary of client/server definition for this guide:
The client is your sys-net disposable.
The server is where you will store your wifi credentials.
The client will ask the server to send the wifi credentials (via stdin/stdout).

2.1 - AdminVM server

In this section, the wifi credentials will be stored in the @adminvm (aka dom0).

2.1.1 - RPC policies

In dom0, create the policies to allow the communication between the server and the client.

echo '
qubes.user.ConnectWifi + sys-net-dvm @adminvm allow
qubes.user.ConnectWifi * @anyvm  @anyvm  deny' \
    | sudo tee -a /etc/qubes/policy.d/30-user.policy > /dev/null

2.1.2 - Server

In dom0, create the script that will send the wifi credentials to the client.

server_script=/etc/qubes-rpc/qubes.user.ConnectWifi
echo "
#!/usr/bin/bash

echo 'MY_SSID' 'MY_PASSWD'" | sudo tee $server_script > /dev/null
sudo chmod +x $server_script

2.1.3 - Client

In your sys-net disposable template, create the script that will connect your wifi.

client_script=/rw/config/qConnectWifi
echo '
#!/usr/bin/bash

read my_ssid my_passwd
nmcli device wifi connect "$my_ssid" password "$my_passwd"' \
    | sudo tee $client_script > /dev/null
sudo chown user:user $client_script
sudo chmod +x $client_script

In your sys-net disposable template, append the below code to the rc.local script.
The sys-net disposable template could be used for all of your system qubes (e.g. sys-net, sys-firewall and sys-usb).
Therefore, we test if the qube is sys-net-dvm.
If this is the case, we wait until the network and Network-Manager are ready, then we make an RPC call to connect our wifi.

echo '
/etc/qubes-rpc/qubes.WaitForSession
if [[ $(qubesdb-read /name) == sys-net-dvm ]]
then
    until systemctl is-active network.target; do sleep 1; done
    nm-online --quiet --wait-for-startup
    qrexec-client-vm @adminvm qubes.user.ConnectWifi '$client_script'
fi' | sudo tee -a /rw/config/rc.local > /dev/null

2.2 - App qube server

In this section, the wifi credentials will be stored in an app qube (e.g vault-wifi).

2.2.1 - RPC policies

In dom0, create the policies (@adminvm is replaced with vault-wifi).

echo '
qubes.user.ConnectWifi + sys-net-dvm vault-wifi allow
qubes.user.ConnectWifi * @anyvm  @anyvm  deny' \
    | sudo tee -a /etc/qubes/policy.d/30-user.policy > /dev/null

https://www.qubes-os.org/doc/admin-api/
In dom0, add these policies.
admin.vm.CurrentState will be used in the client to know when vault-wifi is running/ready.
admin.vm.Start and admin.vm.Shutdown will allow the client to start and stop the server.
These 2 policies (Start and Shutdown) are optional, you could instead make vault-wifi to start automatically on boot.

echo '
admin.vm.CurrentState + sys-net-dvm vault-wifi allow target=@adminvm
admin.vm.Start + sys-net-dvm vault-wifi allow target=@adminvm
admin.vm.Shutdown + sys-net-dvm vault-wifi allow target=@adminvm' \
    | sudo tee -a /etc/qubes/policy.d/30-admin-policy.policy > /dev/null

2.2.2 - Server

In your vault-wifi app qube, create the script to send the wifi credentials.
This time, instead of creating it directly in /etc/qubes-rpc/, let’s store it in the home directory.

server_script=/home/user/qubes_connect_wifi.sh
echo "
#!/usr/bin/bash

echo 'MY_SSID' 'MY_PASSWD'" > $server_script
chmod +x $server_script

As you want to use this script only in the app qube and not in the template, create a symbolic link to /usr/local/etc/qubes-rpc/qubes.user.ConnectWifi (instead of /etc/qubes-rpc/ in the template).

sudo mkdir -p /usr/local/etc/qubes-rpc/
sudo ln -s $server_script /usr/local/etc/qubes-rpc/qubes.user.ConnectWifi

2.2.3 - Client

In your sys-net disposable template, create the script that will connect your wifi.
This is the same one as with dom0 as server.

In your sys-net disposable template, append the below code to the rc.local script.
(optional) We start the vault-wifi qube.
We wait until vault-wifi is running before making the RPC call.
(optional) We shutdown the vault-wifi qube.

client_script=/rw/config/qConnectWifi
echo '
/etc/qubes-rpc/qubes.WaitForSession
if [[ $(qubesdb-read /name) == sys-net-dvm ]]
then
    qrexec-client-vm vault-wifi admin.vm.Start < /dev/null
    while [[ $(qrexec-client-vm vault-wifi admin.vm.CurrentState < /dev/null \
                 | sed -E "s/.*power_state=([^ ]+).*/\1/") != Running ]]
    do
        sleep 1
    done
    until systemctl is-active network.target; do sleep 1; done
    nm-online --quiet --wait-for-startup
    qrexec-client-vm vault-wifi qubes.user.ConnectWifi '$client_script'
    qrexec-client-vm vault-wifi admin.vm.Shutdown < /dev/null
fi' | sudo tee -a /rw/config/rc.local > /dev/null

2.3 - Several connections

In this section, the wifi credentials of several wifi connections will be stored in @adminvm.
This can be adjusted to store them in an app qube.

2.3.1 - RPC policies

In dom0, create the necessary policies.
We use the RPC service argument to differentiate each network (the +something after the service).

echo '
qubes.user.ConnectWifi +home sys-net-dvm @adminvm allow
qubes.user.ConnectWifi +work sys-net-dvm @adminvm allow
qubes.user.ConnectWifi * @anyvm  @anyvm  deny' \
    | sudo tee -a /etc/qubes/policy.d/30-user.policy > /dev/null

2.3.2 - Server

In dom0, create the script that will send the wifi credentials to the client.
We will send different wifi credentials according to the received argument.

server_script=/etc/qubes-rpc/qubes.user.ConnectWifi
echo "
#!/usr/bin/bash

case $1 in
    home) echo 'MY_HOME_SSID' 'MY_HOME_PASSWD';;
    work) echo 'MY_WORK_SSID' 'MY_WORK_PASSWD';;
    *) ;;
esac" | sudo tee $server_script > /dev/null
sudo chmod +x $server_script

2.3.3 - Client

In your sys-net disposable template, create the script that will connect your wifi.
This is the same one as with dom0 as server.

In your sys-net disposable template, append the below code to the rc.local script.
We scan the available wifi connection and connect accordingly.
The first match will be use as an argument to our RPC call.

client_script=/rw/config/qConnectWifi
echo '
/etc/qubes-rpc/qubes.WaitForSession
if [[ $(qubesdb-read /name) == sys-net-dvm ]]
then
    until systemctl is-active network.target; do sleep 1; done
    nm-online --quiet --wait-for-startup
    wifi_ssids=($(nmcli --get-values SSID device wifi))
    rpc_arg=$(for ssid in "${wifi_ssids[@]}"
              do
                  [[ $ssid == home ]] && echo home && break
                  [[ $ssid == work ]] && echo work && break
              done)
    qrexec-client-vm @adminvm qubes.user.ConnectWifi+$rpc_arg '$client_script'
fi' | sudo tee -a /rw/config/rc.local > /dev/null

3 - Remarks


For the RPC service:
It shouldn’t happens, but if there is a race condition and your wifi is not automatically connected.
Open your sys-net disposable terminal and launch the RPC call manually.

[user@sys-net-dvm ~]$ cat /rw/config/rc.local

Copy/paste the qrexec-client-vm call and execute it.

This race condition only happens to me twice in a month of testing.
And the two times was because I had modified dom0 max memory.

5 Likes

Hey, thank you for the guide. With some minor tweaks, I was able to get WiFi to automatically connect on my system with a disposable sys-net and an AppVM that stores the credentials.

Some quick notes on what I had to change to get it to work:

  • By default, /rw/config/rc.local uses #!/bin/sh as the shebang, which means that the script may exit with an error since [[ is a bash builtin and isn’t available in POSIX sh. Fixing it involves either changing the shebang or modifying the script to use to use [ ... ] instead of [[ ... ]]. This also means that == becomes = due to differences in syntax.

  • There are several mentions of sys-net-dvm in code snippets, particularly in the test [[ $(qubesdb-read /name) == sys-net-dvm ]] and in RPC policies. I had to change these to sys-net. Otherwise the script will never execute the relevant parts.

  • The interaction between the script that echoes the SSID + password and the script that reads them can cause problems depending on what the SSID is. If the SSID contains unescaped spaces, both the SSID variable and the password variable will end up with incorrect values, causing a failed connection. Escaping spaces with a backslash fixes this.

Thanks again for the guide. :slight_smile:

1 Like

For anyone wondering where NM-system-connections is coming from, read this: Understanding Qubes /rw folder - #2 by apparatus

Hello,

There was an issue while following these instructions.

For completeness, see the following discussion:

https://forum.qubes-os.org/t/disposable-rw-config-not-saved-on-reboot/31105/6https://forum.qubes-os.org/t/disposable-rw-config-not-saved-on-reboot/31105/6

Thanks for the guide @szz9pza

Hi, thanks for this guide!

For completeness I’ll append another option.

In short: We will use the QubesDB for communication between dom0 and sys-net.

The idea and a prototype with more explanations you can find at Customize a named disposable using the vm-config feature - #22 by parulin.

At first we will use “qvm-features” in dom0 to add the wifi credentials to sys-net:

dom0 - save credentials to sys-net

dom0: ~$ qvm-features sys-net vm-config.wifi-1-name MySSID
dom0: ~$ qvm-features sys-net vm-config.wifi-1-pass MyPassword

We will read these entries with “qubesdb-read /vm-config/…” in sys-net.

As we will do this within a script what shall start automatically when booting sys-net, this script needs to be put at “/rw/config/rc.local.d/” in the named disposable template we use for sys-net. I will name it “ConnectWifi.rc”

In my case I use “sys-mini-dvm” as disposable template for all of my “sys-…” qubes, but you even can use default-dvm. The script will test for the correct qube before doing its work.

In named disposable template for sys-net:

( /rw/config/rc.local.d/ConnectWifi.rc)

#!/bin/bash

if [ $(hostname) = 'sys-net' ]
  then
    WIFI_1_NAME=$(qubesdb-read /vm-config/wifi-1-name -e NOWIFI-name)
    WIFI_1_PWD=$(qubesdb-read /vm-config/wifi-1-pwd -e NOWIFI-pwd)

    if test -n ${WIFI_1_NAME} && [ ${WIFI_1_NAME}!='NOWIFI-name' ] && \
       test -n ${WIFI_1_PWD}  && [  ${WIFI_1_PWD}!='NOWIFI-pwd'  ]
      then      # wifi credentials found

        # waiting for Network-Manager has started ...
	    nm-online -s -t 60

        nmcli device wifi list
        nmcli device wifi rescan

        # workaround for error in some nmcli versions, if connection already exists
        nmcli connection delete "$WIFI_1_NAME"

        # connect to wifi
        nmcli device wifi connect "$WIFI_1_NAME" password "$WIFI_1_PWD"

      else
	echo "no valid credentials found, moving on ..."
    fi
  else
    echo "you are not on sys-net, moving on ..."
fi

Don’t forget to shutdown the disposable template before (re-)starting sys-net!

I should have posted this before but based on @ddevz’s work, I use this Python script:

#!/usr/bin/env python3
"""Check for credentials with qubesdb and connect wifi

Through the qube's features, a list of wifis should be provided
to get the script trying to connect to it. I.e.:

.. code:: console

   [user@dom0] $ qvm-features sys-net
   [...]
   vm-config.wifis name2 1 3
   vm-config.wifis.1.ssid WifiHotspotSSID
   vm-config.wifis.1.password <P455W0RD>
   vm-config.wifis.name2.ssid OtherHotspot
   [...]

The vm-config.wifis feature list the wifis identifiers, by order of priority.
Then, for each wifi ID, SSID and password are provided, as
vm-config.wifis.<IDENTIFIER>.<KEY>
"""

import subprocess

import qubesdb

db = qubesdb.QubesDB()

ROOT_FEATURE_KEY = '/vm-config/wifis'

def connect_wifi(wifi_id: str) -> int:
    """Read the keys of wifi_id and run the appropriate nmcli command

    Return the exit code of nmcli or -1 if there is no SSID associated with the
    wifi_id."""
    key = '.'.join((ROOT_FEATURE_KEY, wifi_id, '{}'))
    ssid = db.read(key.format('ssid')).decode()
    password = db.read(key.format('password')).decode()

    if ssid is None:
        return -1

    process = subprocess.run(['nmcli', 'device', 'wifi', 'connect', f'{ssid}', 'password', f'{password}'])
    return process.returncode

def main():
    raw_wifis = db.read(ROOT_FEATURE_KEY).decode()
    if raw_wifis is None:
        return

    for wifi_id in raw_wifis.split():
        print(f"Try to connect wifi #{wifi_id}")

        if not connect_wifi(wifi_id):
            print(f"\tSuccess")
            return

        print(f"\tFailure")

if __name__ == "__main__":
    main()

The main differences is that it works with more than one pair of credentials and doesn’t check the hostname. The documentation is included in the comments. I combine this with this excellent guide from @Atrate:

Anyway, I think that your post would be a great 3rd solution to this guide. I would just skip the explanations (with a proper link to the other guide explaning the use of vm-config).

1 Like

I’m no python fan and will need some time to understand your script completely. But the idea of having more than 1 SSID is fine.

I’ve given those minimal explanations, so one doesn’t need to read the other sources to understand what happens.

You can do the same thing in the shell script: vm-config.wifis lists the keys used to look for ssids and passwords (I use points as separator but hyphens are fine too), and there is a forloop :slight_smile:

I would replace:

In short: We will use the QubesDB for communication between dom0 and sys-net.

By:

In short: we will use the feature called vm-config for …"

And remove “These credentials we will read with “qubesdb-read /vm-config/…” in sys-net.”

And use default-dvm as an example.