Unverified Commit 88e2e5af authored by Sandro Jäckel's avatar Sandro Jäckel Committed by GitHub
Browse files

ifstate: init at 2.0.0 (#431047)

parents 1bd8afef 8d8c9633
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -68,6 +68,8 @@

- [Newt](https://github.com/fosrl/newt), a fully user space WireGuard tunnel client and TCP/UDP proxy, designed to securely expose private resources controlled by Pangolin. Available as [services.newt](options.html#opt-services.newt.enable).

- [IfState](https://ifstate.net), manage host interface settings in a declarative manner. Available as [networking.ifstate](options.html#opt-networking.ifstate.enable) and [boot.initrd.network.ifstate](options.html#opt-boot.initrd.network.ifstate.enable).

- [qBittorrent](https://www.qbittorrent.org/), is a bittorrent client programmed in C++ / Qt that uses libtorrent by Arvid Norberg. Available as [services.qbittorrent](#opt-services.qbittorrent.enable).

- [Speedify](https://speedify.com/), a proprietary VPN which allows combining multiple internet connections (Wi-Fi, 4G, 5G, Ethernet, Starlink, Satellite, and more) to improve the stability, speed, and security of online experiences. Available as [services.speedify](#opt-services.speedify.enable).
+1 −0
Original line number Diff line number Diff line
@@ -1185,6 +1185,7 @@
  ./services/networking/i2pd.nix
  ./services/networking/icecream/daemon.nix
  ./services/networking/icecream/scheduler.nix
  ./services/networking/ifstate.nix
  ./services/networking/imaginary.nix
  ./services/networking/inadyn.nix
  ./services/networking/inspircd.nix
+311 −0
Original line number Diff line number Diff line
{
  lib,
  config,
  pkgs,
  ...
}:

let
  cfg = config.networking.ifstate;
  initrdCfg = config.boot.initrd.network.ifstate;
  settingsFormat = {
    # override generator in order to:
    # - use yq and not remarshal because it matches yaml datatype handling with IfState
    # - validate json schema
    generate =
      name: value: package:
      pkgs.runCommand name
        {
          nativeBuildInputs = with pkgs; [
            yq
            check-jsonschema
          ];
          value = builtins.toJSON value;
          passAsFile = [ "value" ];
        }
        ''
          yq --yaml-output . $valuePath > $out
          check-jsonschema --schemafile "${cfg.package.passthru.jsonschema}" "$out"
          sed -i $'s|\'!include |!include \'|' $out
        '';

    inherit (pkgs.formats.yaml { }) type;
  };
  initrdInterfaceTypes = builtins.map (interface: interface.link.kind) (
    builtins.attrValues initrdCfg.settings.interfaces
  );
  # IfState interface kind to kernel modules mapping
  interfaceKernelModules = {
    "ifb" = [ "ifb" ];
    "ip6tnl" = [ "ip6tnl" ];
    "ipoib" = [ "ib_ipoib" ];
    "ipvlan" = [ "ipvlan" ];
    "macvlan" = [ "macvlan" ];
    "macvtap" = [ "macvtap" ];
    "team" = [ "team" ];
    "tun" = [ "tun" ];
    "vrf" = [ "vrf" ];
    "vti" = [ "ip_vti" ];
    "vti6" = [ "ip6_vti" ];
    "bond" = [ "bonding" ];
    "bridge" = [ "bridge" ];
    # "physical" = ...;
    "dsa" = [ "dsa_core" ];
    "dummy" = [ "dummy" ];
    "veth" = [ "veth" ];
    "vxcan" = [ "vxcan" ];
    "vlan" = [ "8021q" ];
    "vxlan" = [ "vxlan" ];
    "ipip" = [ "ipip" ];
    "sit" = [ "sit" ];
    "gre" = [ "ip_gre" ];
    "gretap" = [ "ip_gre" ];
    "ip6gre" = [ "ip6_gre" ];
    "ip6gretap" = [ "ip6_gre" ];
    "geneve" = [ "geneve" ];
    "wireguard" = [ "wireguard" ];
    "xfrm" = [ "xfrm_interface" ];
  };
in
{
  meta.maintainers = with lib.maintainers; [ marcel ];

  options = {
    networking.ifstate = {
      enable = lib.mkEnableOption "networking using IfState";

      package = lib.mkPackageOption pkgs "ifstate" { };

      settings = lib.mkOption {
        inherit (settingsFormat) type;
        default = { };
        description = "Content of IfState's configuration file. See <https://ifstate.net/2.0/schema/> for details.";
      };
    };

    boot.initrd.network.ifstate = {
      enable = lib.mkEnableOption "initrd networking using IfState";

      allowIfstateToDrasticlyIncreaseInitrdSize = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "IfState in initrd drastically increases the size of initrd, your boot partition may be too small and/or you may have significantly fewer generations. By setting this option, you acknowledge this fact and keep it in mind when reporting issues.";
      };

      package = lib.mkOption {
        type = lib.types.package;
        default = cfg.package.override {
          withConfigValidation = false;
          withWireguard = false;
        };
        defaultText = lib.literalExpression "pkgs.ifstate.override { withConfigValidation = false; withWireguard = false; }";
        description = "The initrd IfState package to use.";
      };

      settings = lib.mkOption {
        inherit (settingsFormat) type;
        default = { };
        description = "Content of IfState's initrd configuration file. See <https://ifstate.net/2.0/schema/> for details.";
      };

      cleanupSettings = lib.mkOption {
        inherit (settingsFormat) type;
        default = {
          # required by json schema
          interfaces = { };
          # https://codeberg.org/liske/ifstate/issues/118
          namespaces = { };
        };
        description = "Content of IfState's initrd cleanup configuration file. See <https://ifstate.net/2.0/schema/> for details. This configuration gets applied before systemd switches to stage two. The goas is to deconfigurate the whole network in order to prevent access to services, before the firewall is configured. The stage two IfState configuration will start after the firewall is configured.";
      };
    };
  };

  config = lib.mkMerge [
    (lib.mkIf cfg.enable {
      assertions = [
        {
          assertion = !config.networking.networkmanager.enable;
          message = "IfState and NetworkManager cannot be used at the same time, as both configure the network in a conflicting manner.";
        }
        {
          assertion = !config.networking.useDHCP;
          message = "IfState and networking.useDHCP cannot be used at the same time, as both configure the network. Please look into IfState hooks to integrate DHCP: https://codeberg.org/liske/ifstate/issues/111";
        }
      ];

      networking.useDHCP = lib.mkDefault false;

      # sane defaults to not let IfState work against the kernel
      boot.extraModprobeConfig = ''
        options bonding max_bonds=0
        options dummy numdummies=0
        options ifb numifbs=0
      '';

      environment = {
        # ifstatecli command should be available to use user, there are other useful subcommands like check or show
        systemPackages = [ cfg.package ];
        # match the default value of the --config flag of IfState
        etc."ifstate/ifstate.yaml".source = settingsFormat.generate "ifstate.yaml" cfg.settings cfg.package;
      };

      systemd.services.ifstate = {
        description = "IfState";

        wantedBy = [
          "multi-user.target"
        ];
        after = [
          "systemd-udevd.service"
          "network-pre.target"
          "systemd-sysusers.service"
          "systemd-sysctl.service"
        ];
        before = [
          "network.target"
          "multi-user.target"
          "shutdown.target"
          "initrd-switch-root.target"
        ];
        conflicts = [
          "shutdown.target"
          "initrd-switch-root.target"
        ];
        wants = [
          "network.target"
        ];

        # mount is always available on nixos, avoid adding additional store paths to the closure
        path = [ "/run/wrappers" ];

        serviceConfig = {
          Type = "oneshot";
          ExecStart = "${lib.getExe cfg.package} --config ${
            config.environment.etc."ifstate/ifstate.yaml".source
          } apply";
          # because oneshot services do not have a timeout by default
          TimeoutStartSec = "2min";
        };
      };
    })
    (lib.mkIf initrdCfg.enable {
      assertions = [
        {
          assertion =
            initrdCfg.package.passthru.features.withWireguard
            || !(builtins.any (kind: kind == "wireguard") initrdInterfaceTypes);
          message = "IfState initrd package is configured without the `wireguard` feature, but wireguard interfaces are configured. Please see the `boot.initrd.network.ifstate.package` option.";
        }
        {
          assertion = initrdCfg.allowIfstateToDrasticlyIncreaseInitrdSize;
          message = "IfState in initrd drastically increases the size of initrd, your boot partition may be too small and/or you may have significantly fewer generations. By setting boot.initrd.network.initrd.allowIfstateToDrasticlyIncreaseInitrdSize to true, you acknowledge this fact and keep it in mind when reporting issues.";
        }
        {
          assertion = cfg.enable;
          message = "If IfState is used in initrd, it should also be used for the stage 2 system (networking.ifstate), as initrd IfState does not clean up the network stack like it was before after execution.";
        }
        {
          assertion = config.boot.initrd.systemd.enable;
          message = "IfState only supports systemd stage one. See `boot.initrd.systemd.enable` option.";
        }
      ];

      environment.etc = {
        "ifstate/ifstate.initrd.yaml".source =
          settingsFormat.generate "ifstate.initrd.yaml" initrdCfg.settings
            initrdCfg.package;
        "ifstate/ifstate.initrd-cleanup.yaml".source =
          settingsFormat.generate "ifstate.initrd-cleanup.yaml" initrdCfg.cleanupSettings
            initrdCfg.package;
      };

      boot.initrd = {
        network.udhcpc.enable = lib.mkDefault false;

        # automatic configuration of kernel modules of virtual interface types
        availableKernelModules =
          let
            enableModule =
              type:
              if builtins.hasAttr type interfaceKernelModules then interfaceKernelModules."${type}" else [ ];
          in
          lib.flatten (builtins.map enableModule initrdInterfaceTypes);

        systemd = {
          storePaths = [
            (pkgs.runCommand "ifstate-closure"
              {
                info = pkgs.closureInfo {
                  rootPaths = [
                    initrdCfg.package
                    # copy whole config closure, because it can reference other files using !include
                    config.environment.etc."ifstate/ifstate.initrd.yaml".source
                    config.environment.etc."ifstate/ifstate.initrd-cleanup.yaml".source
                  ];
                };
              }
              ''
                mkdir $out
                cat "$info"/store-paths | while read path; do
                  ln -s "$path" "$out/$(basename "$path")"
                done
              ''
            )
          ];

          services.ifstate-initrd = {
            description = "IfState initrd";

            wantedBy = [
              "initrd.target"
            ];
            after = [
              "systemd-udevd.service"
              "network-pre.target"
              "systemd-sysusers.service"
              "systemd-sysctl.service"
            ];
            before = [
              "network.target"
              "multi-user.target"
              "shutdown.target"
              "initrd-switch-root.target"
            ];
            conflicts = [
              "shutdown.target"
              "initrd-switch-root.target"
            ];
            wants = [
              "network.target"
            ];

            # mount is always available on nixos, avoid adding additional store paths to the closure
            # https://github.com/NixOS/nixpkgs/blob/2b8e2457ebe576ebf41ddfa8452b5b07a8d493ad/nixos/modules/system/boot/systemd/initrd.nix#L550-L551
            path = [
              config.boot.initrd.systemd.package.util-linux
            ];

            serviceConfig = {
              Type = "oneshot";
              # Otherwise systemd starts ifstate again, after the encryption password was entered by the user
              # and we are able to implement the cleanup using ExecStop rather than a separate unit.
              RemainAfterExit = true;
              # When using network namespaces pyroute2 expects this directory to exists.
              # @liske is currently investigating whether this should be considered a bug in pyroute2.
              ExecStartPre = "${lib.getExe' pkgs.coreutils "mkdir"} /var/run";
              ExecStart = "${lib.getExe initrdCfg.package} --config ${
                config.environment.etc."ifstate/ifstate.initrd.yaml".source
              } apply";
              ExecStop = "${lib.getExe initrdCfg.package} --config ${
                config.environment.etc."ifstate/ifstate.initrd-cleanup.yaml".source
              } apply";
              # because oneshot services do not have a timeout by default
              TimeoutStartSec = "2min";
            };
          };
        };
      };
    })
  ];
}
+1 −0
Original line number Diff line number Diff line
@@ -735,6 +735,7 @@ in
  i3wm = runTest ./i3wm.nix;
  icingaweb2 = runTest ./icingaweb2.nix;
  ifm = runTest ./ifm.nix;
  ifstate = import ./ifstate { inherit runTest; };
  iftop = runTest ./iftop.nix;
  immich = runTest ./web-apps/immich.nix;
  immich-vectorchord-migration = runTest ./web-apps/immich-vectorchord-migration.nix;
+10 −0
Original line number Diff line number Diff line
{ runTest }:

{
  initrd = runTest ./initrd.nix;
  initrd-partial-broken-config = runTest ./initrd-partial-broken-config.nix;
  initrd-wireguard = runTest ./initrd-wireguard.nix;
  partial-broken-config = runTest ./partial-broken-config.nix;
  ping = runTest ./ping.nix;
  wireguard = runTest ./wireguard.nix;
}
Loading