mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-16 14:54:29 +01:00
Merge pull request #134618 from rnhmjoj/wpa-safe
nixos/wpa_supplicant: add safe secret handling
This commit is contained in:
commit
e68eba2dba
6 changed files with 260 additions and 14 deletions
|
@ -1277,6 +1277,73 @@ Superuser created successfully.
|
|||
</listitem>
|
||||
</itemizedlist>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The
|
||||
<link xlink:href="options.html#opt-networking.wireless.enable">networking.wireless</link>
|
||||
module (based on wpa_supplicant) has been heavily reworked,
|
||||
solving a number of issues and adding useful features:
|
||||
</para>
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
The automatic discovery of wireless interfaces at boot has
|
||||
been made reliable again (issues
|
||||
<link xlink:href="https://github.com/NixOS/nixpkgs/issues/101963">#101963</link>,
|
||||
<link xlink:href="https://github.com/NixOS/nixpkgs/issues/23196">#23196</link>).
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
WPA3 and Fast BSS Transition (802.11r) are now enabled by
|
||||
default for all networks.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Secrets like pre-shared keys and passwords can now be
|
||||
handled safely, meaning without including them in a
|
||||
world-readable file
|
||||
(<literal>wpa_supplicant.conf</literal> under /nix/store).
|
||||
This is achieved by storing the secrets in a secured
|
||||
<link xlink:href="options.html#opt-networking.wireless.environmentFile">environmentFile</link>
|
||||
and referring to them though environment variables that
|
||||
are expanded inside the configuration.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
With multiple interfaces declared, independent
|
||||
wpa_supplicant daemons are started, one for each interface
|
||||
(the services are named
|
||||
<literal>wpa_supplicant-wlan0</literal>,
|
||||
<literal>wpa_supplicant-wlan1</literal>, etc.).
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The generated <literal>wpa_supplicant.conf</literal> file
|
||||
is now formatted for easier reading.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
A new
|
||||
<link xlink:href="options.html#opt-networking.wireless.scanOnLowSignal">scanOnLowSignal</link>
|
||||
option has been added to facilitate fast roaming between
|
||||
access points (enabled by default).
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
A new
|
||||
<link xlink:href="options.html#opt-networking.wireless.networks._name_.authProtocols">networks.<name>.authProtocols</link>
|
||||
option has been added to change the authentication
|
||||
protocols used when connecting to a network.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The
|
||||
|
|
|
@ -390,6 +390,16 @@ In addition to numerous new and upgraded packages, this release has the followin
|
|||
`myhostname`, but before `dns` should use the default priority
|
||||
- NSS modules which should come after `dns` should use mkAfter.
|
||||
|
||||
- The [networking.wireless](options.html#opt-networking.wireless.enable) module (based on wpa_supplicant) has been heavily reworked, solving a number of issues and adding useful features:
|
||||
- The automatic discovery of wireless interfaces at boot has been made reliable again (issues [#101963](https://github.com/NixOS/nixpkgs/issues/101963), [#23196](https://github.com/NixOS/nixpkgs/issues/23196)).
|
||||
- WPA3 and Fast BSS Transition (802.11r) are now enabled by default for all networks.
|
||||
- Secrets like pre-shared keys and passwords can now be handled safely, meaning without including them in a world-readable file (`wpa_supplicant.conf` under /nix/store).
|
||||
This is achieved by storing the secrets in a secured [environmentFile](options.html#opt-networking.wireless.environmentFile) and referring to them though environment variables that are expanded inside the configuration.
|
||||
- With multiple interfaces declared, independent wpa_supplicant daemons are started, one for each interface (the services are named `wpa_supplicant-wlan0`, `wpa_supplicant-wlan1`, etc.).
|
||||
- The generated `wpa_supplicant.conf` file is now formatted for easier reading.
|
||||
- A new [scanOnLowSignal](options.html#opt-networking.wireless.scanOnLowSignal) option has been added to facilitate fast roaming between access points (enabled by default).
|
||||
- A new [networks.<name>.authProtocols](options.html#opt-networking.wireless.networks._name_.authProtocols) option has been added to change the authentication protocols used when connecting to a network.
|
||||
|
||||
- The [networking.wireless.iwd](options.html#opt-networking.wireless.iwd.enable) module has a new [networking.wireless.iwd.settings](options.html#opt-networking.wireless.iwd.settings) option.
|
||||
|
||||
- The [services.syncoid.enable](options.html#opt-services.syncoid.enable) module now properly drops ZFS permissions after usage. Before it delegated permissions to whole pools instead of datasets and didn't clean up after execution. You can manually look this up for your pools by running `zfs allow your-pool-name` and use `zfs unallow syncoid your-pool-name` to clean this up.
|
||||
|
|
|
@ -20,10 +20,16 @@ let
|
|||
++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"''
|
||||
++ optional (cfg.extraConfig != "") cfg.extraConfig);
|
||||
|
||||
configIsGenerated = with cfg;
|
||||
networks != {} || extraConfig != "" || userControlled.enable;
|
||||
|
||||
# the original configuration file
|
||||
configFile =
|
||||
if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable
|
||||
if configIsGenerated
|
||||
then pkgs.writeText "wpa_supplicant.conf" generatedConfig
|
||||
else "/etc/wpa_supplicant.conf";
|
||||
# the config file with environment variables replaced
|
||||
finalConfig = ''"$RUNTIME_DIRECTORY"/wpa_supplicant.conf'';
|
||||
|
||||
# Creates a network block for wpa_supplicant.conf
|
||||
mkNetwork = ssid: opts:
|
||||
|
@ -56,8 +62,8 @@ let
|
|||
let
|
||||
deviceUnit = optional (iface != null) "sys-subsystem-net-devices-${utils.escapeSystemdPath iface}.device";
|
||||
configStr = if cfg.allowAuxiliaryImperativeNetworks
|
||||
then "-c /etc/wpa_supplicant.conf -I ${configFile}"
|
||||
else "-c ${configFile}";
|
||||
then "-c /etc/wpa_supplicant.conf -I ${finalConfig}"
|
||||
else "-c ${finalConfig}";
|
||||
in {
|
||||
description = "WPA Supplicant instance" + optionalString (iface != null) " for interface ${iface}";
|
||||
|
||||
|
@ -69,12 +75,25 @@ let
|
|||
stopIfChanged = false;
|
||||
|
||||
path = [ package ];
|
||||
serviceConfig.RuntimeDirectory = "wpa_supplicant";
|
||||
serviceConfig.RuntimeDirectoryMode = "700";
|
||||
serviceConfig.EnvironmentFile = mkIf (cfg.environmentFile != null)
|
||||
(builtins.toString cfg.environmentFile);
|
||||
|
||||
script =
|
||||
''
|
||||
if [ -f /etc/wpa_supplicant.conf -a "/etc/wpa_supplicant.conf" != "${configFile}" ]; then
|
||||
echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
|
||||
fi
|
||||
${optionalString configIsGenerated ''
|
||||
if [ -f /etc/wpa_supplicant.conf ]; then
|
||||
echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
|
||||
fi
|
||||
''}
|
||||
|
||||
# substitute environment variables
|
||||
${pkgs.gawk}/bin/awk '{
|
||||
for(varname in ENVIRON)
|
||||
gsub("@"varname"@", ENVIRON[varname])
|
||||
print
|
||||
}' "${configFile}" > "${finalConfig}"
|
||||
|
||||
iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"
|
||||
|
||||
|
@ -155,6 +174,44 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/secrets/wireless.env";
|
||||
description = ''
|
||||
File consisting of lines of the form <literal>varname=value</literal>
|
||||
to define variables for the wireless configuration.
|
||||
|
||||
See section "EnvironmentFile=" in <citerefentry>
|
||||
<refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
|
||||
</citerefentry> for a syntax reference.
|
||||
|
||||
Secrets (PSKs, passwords, etc.) can be provided without adding them to
|
||||
the world-readable Nix store by defining them in the environment file and
|
||||
referring to them in option <option>networking.wireless.networks</option>
|
||||
with the syntax <literal>@varname@</literal>. Example:
|
||||
|
||||
<programlisting>
|
||||
# content of /run/secrets/wireless.env
|
||||
PSK_HOME=mypassword
|
||||
PASS_WORK=myworkpassword
|
||||
</programlisting>
|
||||
|
||||
<programlisting>
|
||||
# wireless-related configuration
|
||||
networking.wireless.environmentFile = "/run/secrets/wireless.env";
|
||||
networking.wireless.networks = {
|
||||
home.psk = "@PSK_HOME@";
|
||||
work.auth = '''
|
||||
eap=PEAP
|
||||
identity="my-user@example.com"
|
||||
password="@PASS_WORK@"
|
||||
''';
|
||||
};
|
||||
</programlisting>
|
||||
'';
|
||||
};
|
||||
|
||||
networks = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
|
@ -165,10 +222,14 @@ in {
|
|||
The network's pre-shared key in plaintext defaulting
|
||||
to being a network without any authentication.
|
||||
|
||||
Be aware that these will be written to the nix store
|
||||
in plaintext!
|
||||
<warning><para>
|
||||
Be aware that this will be written to the nix store
|
||||
in plaintext! Use an environment variable instead.
|
||||
</para></warning>
|
||||
|
||||
Mutually exclusive with <varname>pskRaw</varname>.
|
||||
<note><para>
|
||||
Mutually exclusive with <varname>pskRaw</varname>.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -179,7 +240,14 @@ in {
|
|||
The network's pre-shared key in hex defaulting
|
||||
to being a network without any authentication.
|
||||
|
||||
Mutually exclusive with <varname>psk</varname>.
|
||||
<warning><para>
|
||||
Be aware that this will be written to the nix store
|
||||
in plaintext! Use an environment variable instead.
|
||||
</para></warning>
|
||||
|
||||
<note><para>
|
||||
Mutually exclusive with <varname>psk</varname>.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -231,7 +299,7 @@ in {
|
|||
example = ''
|
||||
eap=PEAP
|
||||
identity="user@example.com"
|
||||
password="secret"
|
||||
password="@EXAMPLE_PASSWORD@"
|
||||
'';
|
||||
description = ''
|
||||
Use this option to configure advanced authentication methods like EAP.
|
||||
|
@ -242,7 +310,15 @@ in {
|
|||
</citerefentry>
|
||||
for example configurations.
|
||||
|
||||
Mutually exclusive with <varname>psk</varname> and <varname>pskRaw</varname>.
|
||||
<warning><para>
|
||||
Be aware that this will be written to the nix store
|
||||
in plaintext! Use an environment variable for secrets.
|
||||
</para></warning>
|
||||
|
||||
<note><para>
|
||||
Mutually exclusive with <varname>psk</varname> and
|
||||
<varname>pskRaw</varname>.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -303,11 +379,17 @@ in {
|
|||
default = {};
|
||||
example = literalExample ''
|
||||
{ echelon = { # SSID with no spaces or special characters
|
||||
psk = "abcdefgh";
|
||||
psk = "abcdefgh"; # (password will be written to /nix/store!)
|
||||
};
|
||||
|
||||
echelon = { # safe version of the above: read PSK from the
|
||||
psk = "@PSK_ECHELON@"; # variable PSK_ECHELON, defined in environmentFile,
|
||||
}; # this won't leak into /nix/store
|
||||
|
||||
"echelon's AP" = { # SSID with spaces and/or special characters
|
||||
psk = "ijklmnop";
|
||||
psk = "ijklmnop"; # (password will be written to /nix/store!)
|
||||
};
|
||||
|
||||
"free.wifi" = {}; # Public wireless network
|
||||
}
|
||||
'';
|
||||
|
|
|
@ -479,6 +479,7 @@ in
|
|||
wiki-js = handleTest ./wiki-js.nix {};
|
||||
wireguard = handleTest ./wireguard {};
|
||||
wmderland = handleTest ./wmderland.nix {};
|
||||
wpa_supplicant = handleTest ./wpa_supplicant.nix {};
|
||||
wordpress = handleTest ./wordpress.nix {};
|
||||
xandikos = handleTest ./xandikos.nix {};
|
||||
xautolock = handleTest ./xautolock.nix {};
|
||||
|
|
81
nixos/tests/wpa_supplicant.nix
Normal file
81
nixos/tests/wpa_supplicant.nix
Normal file
|
@ -0,0 +1,81 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ...}:
|
||||
{
|
||||
name = "wpa_supplicant";
|
||||
meta = with lib.maintainers; {
|
||||
maintainers = [ rnhmjoj ];
|
||||
};
|
||||
|
||||
machine = { ... }: {
|
||||
imports = [ ../modules/profiles/minimal.nix ];
|
||||
|
||||
# add a virtual wlan interface
|
||||
boot.kernelModules = [ "mac80211_hwsim" ];
|
||||
|
||||
# wireless access point
|
||||
services.hostapd = {
|
||||
enable = true;
|
||||
wpa = true;
|
||||
interface = "wlan0";
|
||||
ssid = "nixos-test";
|
||||
wpaPassphrase = "reproducibility";
|
||||
};
|
||||
|
||||
# wireless client
|
||||
networking.wireless = {
|
||||
# the override is needed because the wifi is
|
||||
# disabled with mkVMOverride in qemu-vm.nix.
|
||||
enable = lib.mkOverride 0 true;
|
||||
userControlled.enable = true;
|
||||
interfaces = [ "wlan1" ];
|
||||
|
||||
networks = {
|
||||
# test network
|
||||
nixos-test.psk = "@PSK_NIXOS_TEST@";
|
||||
|
||||
# secrets substitution test cases
|
||||
test1.psk = "@PSK_VALID@"; # should be replaced
|
||||
test2.psk = "@PSK_SPECIAL@"; # should be replaced
|
||||
test3.psk = "@PSK_MISSING@"; # should not be replaced
|
||||
test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced
|
||||
};
|
||||
|
||||
# secrets
|
||||
environmentFile = pkgs.writeText "wpa-secrets" ''
|
||||
PSK_NIXOS_TEST="reproducibility"
|
||||
PSK_VALID="S0m3BadP4ssw0rd";
|
||||
# taken from https://github.com/minimaxir/big-list-of-naughty-strings
|
||||
PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~";
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
config_file = "/run/wpa_supplicant/wpa_supplicant.conf"
|
||||
|
||||
with subtest("Configuration file is inaccessible to other users"):
|
||||
machine.wait_for_file(config_file)
|
||||
machine.fail(f"sudo -u nobody ls {config_file}")
|
||||
|
||||
with subtest("Secrets variables have been substituted"):
|
||||
machine.fail(f"grep -q @PSK_VALID@ {config_file}")
|
||||
machine.fail(f"grep -q @PSK_SPECIAL@ {config_file}")
|
||||
machine.succeed(f"grep -q @PSK_MISSING@ {config_file}")
|
||||
machine.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}")
|
||||
|
||||
# save file for manual inspection
|
||||
machine.copy_from_vm(config_file)
|
||||
|
||||
with subtest("Daemon is running and accepting connections"):
|
||||
machine.wait_for_unit("wpa_supplicant-wlan1.service")
|
||||
status = machine.succeed("wpa_cli -i wlan1 status")
|
||||
assert "Failed to connect" not in status, \
|
||||
"Failed to connect to the daemon"
|
||||
|
||||
with subtest("Daemon can connect to the access point"):
|
||||
machine.wait_until_succeeds(
|
||||
"wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
|
||||
)
|
||||
'';
|
||||
})
|
|
@ -1,4 +1,5 @@
|
|||
{ lib, stdenv, fetchurl, fetchpatch, openssl, pkg-config, libnl
|
||||
, nixosTests
|
||||
, withDbus ? true, dbus
|
||||
, withReadline ? true, readline
|
||||
, withPcsclite ? true, pcsclite
|
||||
|
@ -139,6 +140,10 @@ stdenv.mkDerivation rec {
|
|||
install -Dm444 wpa_supplicant.conf $out/share/doc/wpa_supplicant/wpa_supplicant.conf.example
|
||||
'';
|
||||
|
||||
passthru.tests = {
|
||||
inherit (nixosTests) wpa_supplicant;
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
homepage = "https://w1.fi/wpa_supplicant/";
|
||||
description = "A tool for connecting to WPA and WPA2-protected wireless networks";
|
||||
|
|
Loading…
Reference in a new issue