Unverified Commit 9358b3d1 authored by nixpkgs-ci[bot]'s avatar nixpkgs-ci[bot] Committed by GitHub
Browse files

Merge master into staging-nixos

parents 987d17c5 a93e3eba
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -61,6 +61,16 @@ of pulling the upstream container image from Docker Hub. If you want the old beh

- The Bash implementation of the `nixos-rebuild` program is removed. All switchable systems now use the Python rewrite. Any prior usage of `system.rebuild.enableNg` must now be removed. If you have any outstanding issues with the new implementation, please open an issue on GitHub.

- The `networking.wireless` module has been security hardened: the `wpa_supplicant` daemon now runs under an unprivileged user with restricted access to the system.

  As part of these changes, `/etc/wpa_supplicant.conf` has been deprecated: the NixOS-generated configuration file is now linked to `/etc/wpa_supplicant/nixos.conf` and `/etc/wpa_supplicant/imperative.conf` has been added for imperatively configuring `wpa_supplicant` or when using [allowAuxiliaryImperativeNetworks](#opt-networking.wireless.allowAuxiliaryImperativeNetworks).

  If client certificates, keys or other files are needed, these should be stored under `/etc/wpa_supplicant` and owned by `wpa_supplicant` to ensure the daemon can read them.

  Also, the {option}`networking.wireless.userControlled.group` option has been removed since there is now a dedicated `wpa_supplicant` group to control the daemon, and {option}`networking.wireless.userControlled.enable` has been renamed to [](#opt-networking.wireless.userControlled).

  No functionality should have been impacted by these changes (including controlling via `wpa_cli`, integration with NetworkManager or connman), but if you find any problems, please open an issue on GitHub.

- `services.angrr` now uses TOML for configuration. Define policies with `services.angrr.settings` (generate TOML file) or point to a file using `services.angrr.configFile`. The legacy options `services.angrr.period`, `services.angrr.ownedOnly`, and `services.angrr.removeRoot` have been removed. See `man 5 angrr` and the description of `services.angrr.settings` options for examples and details.

- `services.pingvin-share` has been removed as the `pingvin-share.backend` package was broken and the project was archived upstream.
+1 −0
Original line number Diff line number Diff line
@@ -165,6 +165,7 @@ in
      wireless = {
        enable = lib.mkIf (!enableIwd) true;
        dbusControlled = true;
        autoDetectInterfaces = false;
        iwd = lib.mkIf enableIwd {
          enable = true;
        };
+12 −7
Original line number Diff line number Diff line
@@ -11,7 +11,8 @@ let
  cfg = config.networking.networkmanager;
  ini = pkgs.formats.ini { };

  delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [ ];
  # Whether wpa_supplicant is managed independently
  delegateWireless = config.networking.wireless.networks != { } && cfg.unmanaged != [ ];

  enableIwd = cfg.wifi.backend == "iwd";

@@ -136,10 +137,7 @@ let
    cfg.package
  ]
  ++ cfg.plugins
  ++ pluginRuntimeDeps
  ++ lib.optionals (!delegateWireless && !enableIwd) [
    pkgs.wpa_supplicant
  ];
  ++ pluginRuntimeDeps;
in
{

@@ -541,9 +539,9 @@ in

    assertions = [
      {
        assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [ ];
        assertion = config.networking.wireless.networks != { } -> cfg.unmanaged != [ ];
        message = ''
          You can not use networking.networkmanager with networking.wireless.
          You can not use networking.networkmanager with networking.wireless.networks.
          Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
        '';
      }
@@ -676,6 +674,13 @@ in
        useDHCP = false;
      })

      (mkIf (!delegateWireless && !enableIwd) {
        # Enable wpa_supplicant but fully control it over DBus
        wireless.enable = true;
        wireless.autoDetectInterfaces = false;
        wireless.dbusControlled = true;
      })

      (mkIf enableIwd {
        wireless.iwd.enable = true;
      })
+157 −71
Original line number Diff line number Diff line
@@ -48,31 +48,6 @@ let
    else
      networkList;

  # Content of wpa_supplicant.conf
  generatedConfig = concatStringsSep "\n" (
    (map mkNetwork allNetworks)
    ++ optional cfg.userControlled.enable (
      concatStringsSep "\n" [
        "ctrl_interface=/run/wpa_supplicant"
        "ctrl_interface_group=${cfg.userControlled.group}"
        "update_config=1"
      ]
    )
    ++ [ "pmf=1" ]
    ++ optional (cfg.secretsFile != null) "ext_password_backend=file:${cfg.secretsFile}"
    ++ 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 configIsGenerated then
      pkgs.writeText "wpa_supplicant.conf" generatedConfig
    else
      "/etc/wpa_supplicant.conf";

  # Creates a network block for wpa_supplicant.conf
  mkNetwork =
    opts:
@@ -104,6 +79,12 @@ let
      }
    '';

  hasDeclarative = lib.any id [
    (cfg.networks != { })
    (cfg.extraConfig != "")
    cfg.userControlled
  ];

  # Creates a systemd unit for wpa_supplicant bound to a given (or any) interface
  mkUnit =
    iface:
@@ -114,9 +95,11 @@ let
      configStr =
        (
          if cfg.allowAuxiliaryImperativeNetworks then
            "-c /etc/wpa_supplicant.conf -I ${configFile}"
            "-c /etc/wpa_supplicant/imperative.conf -I /etc/wpa_supplicant/nixos.conf"
          else if hasDeclarative then
            "-c /etc/wpa_supplicant/nixos.conf"
          else
            "-c ${configFile}"
            "-c /etc/wpa_supplicant/imperative.conf"
        )
        + lib.concatMapStrings (p: " -I " + p) cfg.extraConfigFiles;
    in
@@ -128,32 +111,100 @@ let
      wants = [ "network.target" ];
      requires = deviceUnit;
      wantedBy = [ "multi-user.target" ];

      stopIfChanged = false;
      restartTriggers = [ config.environment.etc."wpa_supplicant/nixos.conf".source ];

      path = [ pkgs.wpa_supplicant ];
      # if `userControl.enable`, the supplicant automatically changes the permissions
      #  and owning group of the runtime dir; setting `umask` ensures the generated
      #  config file isn't readable (except to root);  see nixpkgs#267693
      serviceConfig.UMask = "066";
      serviceConfig.RuntimeDirectory = "wpa_supplicant";
      serviceConfig.RuntimeDirectoryMode = "700";
      serviceConfig = {
        User = "wpa_supplicant";
        Group = "wpa_supplicant";
        RuntimeDirectory = "wpa_supplicant";
        AmbientCapabilities = [
          "CAP_NET_ADMIN"
          "CAP_NET_RAW"
        ];
        CapabilityBoundingSet = [
          "CAP_NET_ADMIN"
          "CAP_NET_RAW"
        ];
        RootDirectory = "/run/wpa_supplicant";
        RootDirectoryStartOnly = true;
        BindPaths = [
          "/etc/wpa_supplicant" # to write wpa_supplicant.conf{,.tmp}
          "/run/wpa_supplicant" # to make control sockets
          # to set up interfaces
          "/proc/sys/net"
          "/dev/rfkill"
        ]
        ++ lib.optional cfg.dbusControlled "/run/dbus"
        ++ lib.optional cfg.allowAuxiliaryImperativeNetworks "/etc/wpa_supplicant";
        BindReadOnlyPaths = [
          builtins.storeDir
          "/etc/"
        ]
        ++ lib.optional (cfg.secretsFile != null) cfg.secretsFile;
        DeviceAllow = "/dev/rfkill rw";
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        NoNewPrivileges = true;
        PrivateDevices = true;
        PrivateMounts = true;
        PrivateTmp = true;
        PrivateUsers = false;
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        ProtectSystem = "strict";
        IPAddressDeny = "any";
        RemoveIPC = true;
        RestrictAddressFamilies = [
          "AF_UNIX"
          "AF_INET"
          "AF_INET6"
          "AF_NETLINK"
          "AF_PACKET"
        ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        SystemCallFilter = [
          "@system-service"
          "~@keyring"
          "~@resources"
        ];
        SystemCallArchitectures = "native";
        UMask = "0077";

        ExecStartPre =
          lib.optionals (cfg.allowAuxiliaryImperativeNetworks || !hasDeclarative) [
            # set up imperative config file
            "+${pkgs.coreutils}/bin/touch /etc/wpa_supplicant/imperative.conf"
            "+${pkgs.coreutils}/bin/chmod 664 /etc/wpa_supplicant/imperative.conf"
            "+${pkgs.coreutils}/bin/chown -R wpa_supplicant:wpa_supplicant /etc/wpa_supplicant"
          ]
          ++ lib.optionals cfg.userControlled [
            # set up client sockets directory
            "+${pkgs.coreutils}/bin/mkdir /run/wpa_supplicant/client"
            "+${pkgs.coreutils}/bin/chown wpa_supplicant:wpa_supplicant /run/wpa_supplicant/client"
            "+${pkgs.coreutils}/bin/chmod g=u /run/wpa_supplicant/client"
          ];
      };

      script = ''
        ${optionalString (configIsGenerated && !cfg.allowAuxiliaryImperativeNetworks) ''
          if [ -f /etc/wpa_supplicant.conf ]; then
            echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
          fi
        ''}

        # ensure wpa_supplicant.conf exists, or the daemon will fail to start
        ${optionalString cfg.allowAuxiliaryImperativeNetworks ''
          touch /etc/wpa_supplicant.conf
        ''}

        iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"

        ${
          if iface == null then
          if iface != null then
            ''
              # add known interface to the daemon arguments
              args="-i${iface} $iface_args"
            ''
          else if cfg.autoDetectInterfaces then
            ''
              # detect interfaces automatically

@@ -176,10 +227,7 @@ let
              done
            ''
          else
            ''
              # add known interface to the daemon arguments
              args="-i${iface} $iface_args"
            ''
            "args=$iface_args"
        }

        # finally start daemon
@@ -205,7 +253,8 @@ in
          "wlan1"
        ];
        description = ''
          The interfaces {command}`wpa_supplicant` will use. If empty, it will
          The interfaces {command}`wpa_supplicant` will use. If empty and
          [](#opt-networking.wireless.autoDetectInterfaces) is true it will
          automatically use all wireless interfaces.

          ::: {.note}
@@ -214,6 +263,10 @@ in
        '';
      };

      autoDetectInterfaces = mkEnableOption "automatic detection of wireless interfaces" // {
        default = true;
      };

      driver = mkOption {
        type = types.str;
        default = "nl80211,wext";
@@ -503,29 +556,38 @@ in
        '';
      };

      userControlled = {
        enable = mkOption {
          type = types.bool;
      userControlled = mkOption {
        type =
          with types;
          coercedTo attrs (
            val:
            if builtins.isAttrs val && val ? enable then
              trace "Obsolete option `networking.wireless.userControlled.enable' is used. It was renamed to networking.wireless.userControlled" val.enable
            else if builtins.isAttrs val && val ? group then
              trace
                "The option definition `networking.wireless.userControlled.group' no longer has any effect. The group is now fixed to `wpa_supplicant'."
                (val.enable or false)
            else if builtins.isBool val then
              val
            else
              false
          ) bool;
        default = false;
        description = ''
            Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
          Allow users of the `wpa_supplicant` group to control wpa_supplicant
          through wpa_gui or wpa_cli.
          This is useful for laptop users that switch networks a lot and don't want
          to depend on a large package such as NetworkManager just to pick nearby
          access points.

            When using a declarative network specification you cannot persist any
            settings via wpa_gui or wpa_cli.
          ::: {.note}
          When networks are configured declaratively, you cannot persist any settings
          via wpa_gui or wpa_cli, unless {option}`allowAuxiliaryImperativeNetworks`
          is used.
          :::
        '';
      };

        group = mkOption {
          type = types.str;
          default = "wheel";
          example = "network";
          description = "Members of this group can control wpa_supplicant.";
        };
      };

      dbusControlled = mkOption {
        type = types.bool;
        default = lib.length cfg.interfaces < 2;
@@ -624,9 +686,33 @@ in
        }
      ];

    users.groups.wpa_supplicant = { };
    users.users.wpa_supplicant = {
      isSystemUser = true;
      group = "wpa_supplicant";
      description = "WPA Supplicant user";
    };

    hardware.wirelessRegulatoryDatabase = true;

    environment.systemPackages = [ pkgs.wpa_supplicant ];

    # NixOS-generated configuration files
    environment.etc."wpa_supplicant/nixos.conf".text = concatStringsSep "\n" (
      (map mkNetwork allNetworks)
      ++ optional cfg.userControlled (
        concatStringsSep "\n" [
          "ctrl_interface=/run/wpa_supplicant/control"
          "ctrl_interface_group=wpa_supplicant"
          "update_config=1"
        ]
      )
      ++ [ "pmf=1" ]
      ++ optional (cfg.secretsFile != null) "ext_password_backend=file:${cfg.secretsFile}"
      ++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"''
      ++ optional (cfg.extraConfig != "") cfg.extraConfig
    );

    services.dbus.packages = optional cfg.dbusControlled pkgs.wpa_supplicant;

    systemd.services =
+38 −17
Original line number Diff line number Diff line
@@ -94,20 +94,30 @@ let
          };
        };

        # Note: secrets are stored outside /etc/ and /nix/store to
        # test for accessibility of these paths
        system.activationScripts.wpa-secrets = {
          deps = [
            "users"
            "specialfs"
          ];
          text = ''
            install -Dm600 -o wpa_supplicant ${pkgs.writeText "wpa" ''
              psk_nixos_test=${naughtyPassphrase}
            ''} /var/lib/secrets/wpa
          '';
        };

        # wireless client
        networking.wireless = lib.mkMerge [
          {
            # the override is needed because the wifi is
            # disabled with mkVMOverride in qemu-vm.nix.
            enable = lib.mkOverride 0 true;
            userControlled.enable = true;
            userControlled = true;
            interfaces = [ "wlan1" ];
            fallbackToWPA2 = lib.mkDefault true;

            # secrets
            secretsFile = pkgs.writeText "wpa-secrets" ''
              psk_nixos_test=${naughtyPassphrase}
            '';
            secretsFile = "/var/lib/secrets/wpa";
          }
          extraConfig
        ];
@@ -142,7 +152,8 @@ in
        # the override is needed because the wifi is
        # disabled with mkVMOverride in qemu-vm.nix.
        enable = lib.mkOverride 0 true;
        userControlled.enable = true;
        userControlled = true;
        dbusControlled = true;
        fallbackToWPA2 = true;

        networks = {
@@ -198,9 +209,14 @@ in
          assert "Failed to connect" not in status, \
                 "Failed to connect to the daemon"

      # get the configuration file
      cmdline = machine.succeed("cat /proc/$(pgrep wpa)/cmdline").split('\x00')
      config_file = cmdline[cmdline.index("-c") + 1]
      with subtest("D-Bus interface is working"):
          dbus_command = "dbus-send --system --print-reply --dest=fi.w1.wpa_supplicant1 " \
                         "/fi/w1/wpa_supplicant1 fi.w1.wpa_supplicant1.GetInterface string:wlan0"
          machine.succeed(dbus_command)  # as root
          machine.succeed(f"sudo -g wpa_supplicant {dbus_command}")  # as wpa_supplicant group

      # generated configuration file
      config_file = "/etc/static/wpa_supplicant/nixos.conf"

      with subtest("WPA2 fallbacks have been generated"):
          assert int(machine.succeed(f"grep -c sae-only {config_file}")) == 1
@@ -218,6 +234,9 @@ in

      # save file for manual inspection
      machine.copy_from_vm(config_file)

      # check hardening options
      machine.succeed("systemd-analyze security wpa_supplicant >&2")
    '';
  };

@@ -233,25 +252,27 @@ in
      # wireless client
      networking.wireless = {
        enable = lib.mkOverride 0 true;
        userControlled.enable = true;
        userControlled = true;
        allowAuxiliaryImperativeNetworks = true;
        interfaces = [ "wlan1" ];
      };
    };

    testScript = ''
      wpa_cli = "sudo -u nobody -g wpa_supplicant wpa_cli"

      with subtest("Daemon is running and accepting connections"):
          machine.wait_for_unit("wpa_supplicant-wlan1.service")
          status = machine.wait_until_succeeds("wpa_cli -i wlan1 status")
          status = machine.wait_until_succeeds(f"{wpa_cli} -i wlan1 status")
          assert "Failed to connect" not in status, \
                 "Failed to connect to the daemon"

      with subtest("Daemon can be configured imperatively"):
          machine.succeed("wpa_cli -i wlan1 add_network")
          machine.succeed("wpa_cli -i wlan1 set_network 0 ssid '\"nixos-test\"'")
          machine.succeed("wpa_cli -i wlan1 set_network 0 psk '\"reproducibility\"'")
          machine.succeed("wpa_cli -i wlan1 save_config")
          machine.succeed("grep -q nixos-test /etc/wpa_supplicant.conf")
          machine.succeed(f"{wpa_cli} -i wlan1 add_network")
          machine.succeed(f"{wpa_cli} -i wlan1 set_network 0 ssid '\"nixos-test\"'")
          machine.succeed(f"{wpa_cli} -i wlan1 set_network 0 psk '\"reproducibility\"'")
          machine.succeed(f"{wpa_cli} -i wlan1 save_config")
          machine.succeed("grep -q nixos-test /etc/wpa_supplicant/imperative.conf")
    '';
  };

Loading