Unverified Commit cb285783 authored by Sizhe Zhao's avatar Sizhe Zhao
Browse files

nixos/firewalld: init

parent 20db2d7d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1165,6 +1165,7 @@
  ./services/networking/firewall-iptables.nix
  ./services/networking/firewall-nftables.nix
  ./services/networking/firewall.nix
  ./services/networking/firewalld
  ./services/networking/firezone/gateway.nix
  ./services/networking/firezone/gui-client.nix
  ./services/networking/firezone/headless-client.nix
+66 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  pkgs,
  ...
}:

let
  cfg = config.services.firewalld;
  paths = pkgs.buildEnv {
    name = "firewalld-paths";
    paths = cfg.packages;
    pathsToLink = [ "/lib/firewalld" ];
  };
in
{
  imports = [
    ./service.nix
    ./settings.nix
    ./zone.nix
  ];

  options.services.firewalld = {
    enable = lib.mkEnableOption "FirewallD";
    package = lib.mkPackageOption pkgs "firewalld" { };
    packages = lib.mkOption {
      type = lib.types.listOf lib.types.package;
      default = [ ];
      description = ''
        Packages providing firewalld zones and other files.
        Files found in `/lib/firewalld` will be included.
      '';
    };
    extraArgs = lib.mkOption {
      type = lib.types.listOf lib.types.str;
      default = [ ];
      example = [ "--debug" ];
      description = "Extra arguments to pass to FirewallD.";
    };
  };

  config = lib.mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ];
    services.dbus.packages = [ cfg.package ];
    services.firewalld.packages = [ cfg.package ];

    services.logrotate.settings."/var/log/firewalld" = {
      copytruncate = true;
      minsize = "1M";
    };

    environment.etc."sysconfig/firewalld".text = ''
      FIREWALLD_ARGS=${lib.concatStringsSep " " cfg.extraArgs}
    '';

    systemd.packages = [ cfg.package ];
    systemd.services.firewalld = {
      aliases = [ "dbus-org.fedoraproject.FirewallD1.service" ];
      wantedBy = [ "multi-user.target" ];
      serviceConfig.ExecReload = "${lib.getExe' pkgs.coreutils "kill"} -HUP $MAINPID";
      environment.NIX_FIREWALLD_CONFIG_PATH = "${paths}/lib/firewalld";
    };
  };

  meta.maintainers = with lib.maintainers; [ prince213 ];
}
+55 −0
Original line number Diff line number Diff line
{ lib }:

let
  inherit (lib) mkOption;
  inherit (lib.types)
    either
    enum
    nullOr
    port
    submodule
    ;
  mkPortOption =
    {
      optional ? false,
    }:
    mkOption {
      type =
        let
          type = either port (submodule {
            options = {
              from = mkOption { type = port; };
              to = mkOption { type = port; };
            };
          });
        in
        if optional then (nullOr type) else type;
      description = "";
      apply =
        value: if builtins.isAttrs value then "${toString value.from}-${toString value.to}" else value;
    };
  protocolOption = mkOption {
    type = enum [
      "tcp"
      "udp"
      "sctp"
      "dccp"
    ];
    description = "";
  };
in
{
  inherit mkPortOption;
  inherit protocolOption;

  toXmlAttrs = lib.mapAttrs' (name: lib.nameValuePair ("@" + name));
  mkXmlAttr = name: value: { "@${name}" = value; };
  filterNullAttrs = lib.filterAttrsRecursive (_: value: value != null);

  portProtocolOptions = {
    options = {
      port = mkPortOption { };
      protocol = protocolOption;
    };
  };
}
+121 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  pkgs,
  ...
}:

let
  cfg = config.services.firewalld;
  format = pkgs.formats.xml { };
  lib' = import ./lib.nix { inherit lib; };
  inherit (lib')
    filterNullAttrs
    mkXmlAttr
    portProtocolOptions
    toXmlAttrs
    ;
  inherit (lib) mkOption;
  inherit (lib.types)
    attrsOf
    listOf
    nonEmptyStr
    nullOr
    strMatching
    submodule
    ;
in
{
  options.services.firewalld.services = mkOption {
    description = ''
      firewalld service configuration files. See {manpage}`firewalld.service(5)`.
    '';
    default = { };
    type = attrsOf (submodule {
      options = {
        version = mkOption {
          type = nullOr nonEmptyStr;
          description = "Version of the service.";
          default = null;
        };
        short = mkOption {
          type = nullOr nonEmptyStr;
          description = "Short description for the service.";
          default = null;
        };
        description = mkOption {
          type = nullOr nonEmptyStr;
          description = "Description for the service.";
          default = null;
        };
        ports = mkOption {
          type = listOf (submodule portProtocolOptions);
          description = "Ports of the service.";
          default = [ ];
        };
        protocols = mkOption {
          type = listOf nonEmptyStr;
          description = "Protocols for the service.";
          default = [ ];
        };
        sourcePorts = mkOption {
          type = listOf (submodule portProtocolOptions);
          description = "Source ports for the service.";
          default = [ ];
        };
        destination = mkOption {
          type = submodule {
            options = {
              ipv4 = mkOption {
                type = nullOr (strMatching "([0-9]{1,3}\\.){3}[0-9]{1,3}(/[0-9]{1,2})?");
                description = "IPv4 destination.";
                default = null;
              };
              ipv6 = mkOption {
                type = nullOr (strMatching "[0-9A-Fa-f:]{3,39}(/[0-9]{1,3})?");
                description = "IPv6 destination.";
                default = null;
              };
            };
          };
          description = "Destinations for the service.";
          default = { };
        };
        includes = mkOption {
          type = listOf nonEmptyStr;
          description = "Services to include for the service.";
          default = [ ];
        };
        helpers = mkOption {
          type = listOf nonEmptyStr;
          description = "Helpers for the service.";
          default = [ ];
        };
      };
    });
  };

  config = lib.mkIf cfg.enable {
    environment.etc = lib.mapAttrs' (
      name: value:
      lib.nameValuePair "firewalld/services/${name}.xml" {
        source = format.generate "firewalld-service-${name}.xml" {
          service = filterNullAttrs (
            lib.mergeAttrsList [
              (toXmlAttrs { inherit (value) version; })
              {
                inherit (value) short description;
                port = builtins.map toXmlAttrs value.ports;
                protocol = builtins.map (mkXmlAttr "value") value.protocols;
                source-port = builtins.map toXmlAttrs value.sourcePorts;
                destination = toXmlAttrs value.destination;
                include = builtins.map (mkXmlAttr "service") value.includes;
                helper = builtins.map (mkXmlAttr "name") value.helpers;
              }
            ]
          );
        };
      }
    ) cfg.services;
  };
}
+198 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  pkgs,
  ...
}:

let
  cfg = config.services.firewalld;
  format = pkgs.formats.keyValue { };
  inherit (lib) mkOption;
  inherit (lib.types)
    bool
    commas
    either
    enum
    nonEmptyStr
    separatedString
    submodule
    ;
in
{
  options.services.firewalld.settings = mkOption {
    description = ''
      FirewallD config file.
      See {manpage}`firewalld.conf(5)`.
    '';
    default = { };
    type = submodule {
      freeformType = format.type;
      options = {
        DefaultZone = mkOption {
          type = nonEmptyStr;
          description = "Default zone for connections.";
          default = "public";
        };
        CleanupOnExit = mkOption {
          type = bool;
          description = "Whether to clean up firewall rules when firewalld stops.";
          default = true;
        };
        CleanupModulesOnExit = mkOption {
          type = bool;
          description = "Whether to unload all firewall-related kernel modules when firewalld stops.";
          default = false;
        };
        IPv6_rpfilter = mkOption {
          type = enum [
            "strict"
            "loose"
            "strict-forward"
            "loose-forward"
            "no"
          ];
          description = ''
            Performs reverse path filtering (RPF) on IPv6 packets as per RFC 3704.

            Possible values:

            `"strict"`
            : Performs "strict" filtering as per RFC 3704.
              This check verifies that the in ingress interface is the same interface that would be used to send a packet reply to the source.
              That is, `ingress == egress`.

            `"loose"`
            : Performs "loose" filtering as per RFC 3704.
              This check only verifies that there is a route back to the source through any interface; even if it's not the same one on which the packet arrived.

            `"strict-forward"`
            : This is almost identical to "strict", but does not perform RPF for packets targeted to the host (INPUT).

            `"loose-forward"`
            : This is almost identical to "loose", but does not perform RPF for packets targeted to the host (INPUT).

            `"no"`
            : RPF is completely disabled.

            The rp_filter for IPv4 is controlled using sysctl.
          '';
          default = "strict";
        };
        IndividualCalls = mkOption {
          type = bool;
          description = ''
            Whether to use individual -restore calls to apply changes to the firewall.
            The use of individual calls increases the time that is needed to apply changes and to start the daemon, but is good for debugging as error messages are more specific.
          '';
          default = false;
        };
        LogDenied = mkOption {
          type = enum [
            "all"
            "unicast"
            "broadcast"
            "multicast"
            "off"
          ];
          description = ''
            Add logging rules right before reject and drop rules in the INPUT, FORWARD and OUTPUT chains for the default rules and also final reject and drop rules in zones for the configured link-layer packet type.
          '';
          default = "off";
        };
        FirewallBackend = mkOption {
          type = enum [
            "nftables"
            "iptables"
          ];
          description = ''
            The firewall backend implementation.
            This applies to all firewalld primitives.
            The only exception is direct and passthrough rules which always use the traditional iptables, ip6tables, and ebtables backends.

            ::: {.caution}
            The iptables backend is deprecated.
            It will be removed in a future release.
            :::
          '';
          default = "nftables";
        };
        FlushAllOnReload = mkOption {
          type = bool;
          description = "Whether to flush all runtime rules on a reload.";
          default = true;
        };
        ReloadPolicy = mkOption {
          type =
            let
              policy = enum [
                "DROP"
                "REJECT"
                "ACCEPT"
              ];
            in
            either policy commas;
          description = "The policy during reload.";
          default = "INPUT:DROP,FORWARD:DROP,OUTPUT:DROP";
        };
        RFC3964_IPv4 = mkOption {
          type = bool;
          description = ''
            Whether to filter IPv6 traffic with 6to4 destination addresses that correspond to IPv4 addresses that should not be routed over the public internet.
          '';
          default = true;
        };
        StrictForwardPorts = mkOption {
          type = bool;
          description = ''
            If enabled, the generated destination NAT (DNAT) rules will NOT accept traffic that was DNAT'd by other entities, e.g. docker.
            Firewalld will be strict and not allow published container ports until they're explicitly allowed via firewalld.
            If set to `false`, then docker (and podman) integrates seamlessly with firewalld.
            Published container ports are implicitly allowed.
          '';
          default = false;
        };
        NftablesFlowtable = mkOption {
          type = separatedString " ";
          description = ''
            This may improve forwarded traffic throughput by enabling nftables flowtable.
            It is a software fastpath and avoids calling nftables rule evaluation for data packets.
            Its value is a space separate list of interfaces.
          '';
          default = "off";
        };
        NftablesCounters = mkOption {
          type = bool;
          description = "Whether to add a counter to every nftables rule.";
          default = false;
        };
        NftablesTableOwner = mkOption {
          type = bool;
          description = ''
            If enabled, the generated nftables rule set will be owned exclusively by firewalld.
            This prevents other entities from mistakenly (or maliciously) modifying firewalld's rule set.
            If you intend to modify firewalld's rules, set this to `false`.
          '';
          default = true;
        };
      };
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion = cfg.settings.FirewallBackend == "nftables" -> config.networking.nftables.enable;
        message = ''
          FirewallD uses nftables as the firewall backend (by default), but nftables support isn't enabled.
          Please read the description of networking.nftables.enable for possible problems.
          If using nftables is not desired, set services.firewalld.settings.FirewallBackend to "iptables", but be aware that FirewallD has deprecated support for it, and will override firewall rule set by other services, if any.
        '';
      }
    ];

    environment.etc."firewalld/firewalld.conf" = {
      source = format.generate "firewalld.conf" cfg.settings;
    };
  };
}
Loading