Imperative & Declarative Wifi networks with wpa_supplicant

I recently opened https://github.com/NixOS/nixpkgs/pull/113716 which aims to allow both declarative networks (i.e. defining wifi networks for wpa_supplicant in networking.wireless.networks) and imperative networks (i.e. writing them imperatively to /etc/wpa_supplicant.conf, e.g. via wpa_gui).

The reason for this is that I have a fair share of networks that I want to have “just available” on my config and more sensitive things (such as WPA2 enterprise credentials for university and my employer’s office) that I don’t want to have in the store on my laptop. Right now, you have to choose between imperative or declarative networks, but can’t use both which is something this PR aims to change.

First of all, let me explain how:

  • wpa_supplicant has two config files (the “default one” specified with -c) and a second one specified with -I.
    • The second one is only supposed to be used for “global” settings, however it’s possible to write networks into it.
    • However all networks (from both files) will be written to /etc/wpa_supplicant.conf e.g. when wpa_gui instructs the daemon to save all changes. This also means, that declarative networks would basically “become” imperative networks if you use -I for a store-path and -c for imperative networks.
  • I worked around this by patching wpa_supplicant to ignore networks from the file specified with -I, so wpa_gui and wpa_supplicant treat these as “immutable” while you can still declare networks (and also custom settings) imperatively in /etc/wpa_supplicant.conf.

I’m writing this up here because I’d love to get a bit more feedback on this. I know that patching wpa_supplicant here for basically a new feature is fairly invasive, so I don’t want to see this in master before getting more feedback. I already had a longer discussion with @bb2020 about this which may provide more context. We also agreed that before this is fully ready, the behavior should be made opt-in at least.

I’m already using this patch for a while without any issues so far. But would be cool to get a few more opinions on this :slight_smile:

Some random thoughts:

  • NetworkManager is a frontend to networking tools, and IIRC it’s in the process of switching from wpa_supplicant backend to iwd. the iwd backend is already supported in NixOS.
  • What are the opportunities for connecting to wifi networks securely using the secrets managers?

First of all, thanks a lot for your input!

NetworkManager is a frontend to networking tools, and IIRC it’s in the process of switching from wpa_supplicant backend to iwd 2. the iwd backend is already supported in NixOS.

Well, I had enough of iwd and switched back to wpa_supplicant a while ago, so nothing I’m particularly interested in :slight_smile:

Also this change only applies to wpa_supplicant (the module and the package) itself. Not sure if e.g. declarative are even possible with network manager or am I misunderstanding your point here?

What are the opportunities for connecting to wifi networks securely using the secrets managers 3?

I think it would be possible to use e.g. wpa_supplicant -I /run/secrets/mynetworks.conf in cojunction with my patch (to avoid that these are written to /etc/wpa_supplicant.conf for the reasons I outlined in my OP).

Please keep in mind that this wasn’t supported before and is IMHO out of scope for the current PR. But don’t get me wrong, I’d be open to discuss this as well in the future!

1 Like

It should be possible to put a file in /etc/NetworkManager/system-connections/mywifi.nmconnection declaratively.

I marked the PR as “ready for review” now. The behavior is still opt-in of course. I’ll wait for a while, but I’d consider this as ready now (I’m also running it on my machine for >2 months).

I went another way, which is to autogenerate the /etc/NetworkManager/system-connections/*.nmconnection files, by using the following module :

{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.networking.networkmanager;

  getFileName = stringAsChars (x: if x == " " then "-" else x);

  createWifi = ssid: opt: {
    name = ''
      NetworkManager/system-connections/${getFileName ssid}.nmconnection
    '';
    value = {
      mode = "0400";
      source = pkgs.writeText "${ssid}.nmconnection" ''
        [connection]
        id=${ssid}
        type=wifi

        [wifi]
        ssid=${ssid}

        [wifi-security]
        ${optionalString (opt.psk != null) ''
        key-mgmt=wpa-psk
        psk=${opt.psk}''}
      '';
    };
  };

  keyFiles = mapAttrs' createWifi config.networking.wireless.networks;
in {
  config = mkIf cfg.enable {
    environment.etc = keyFiles;

    systemd.services.NetworkManager-predefined-connections = {
      restartTriggers = mapAttrsToList (name: value: value.source) keyFiles;
      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStart = "${pkgs.coreutils}/bin/true";
        ExecReload = "${pkgs.networkmanager}/bin/nmcli connection reload";
      };
      reloadIfChanged = true;
      wantedBy = [ "multi-user.target" ];
    };
  };
}

with

networking.networkmanager.enable = true;
networking.wireless.networks."SSID" = { psk = "KEY" };
1 Like