From 02abf4ed0131237c25e0a10db50fa4c41a902a50 Mon Sep 17 00:00:00 2001 From: Alejandro Soto Date: Sun, 14 Jul 2024 17:53:13 -0600 Subject: sys: final merge of dmz, hv into sys --- sys/auth.nix | 82 -------- sys/auth/default.nix | 6 + sys/auth/oath.nix | 34 ++++ sys/auth/openssh.nix | 86 ++++++++ sys/baseline/default.nix | 61 ++++++ sys/boot.nix | 116 ----------- sys/boot/chain.nix | 37 ++++ sys/boot/default.nix | 13 ++ sys/boot/detached-luks.nix | 73 +++++++ sys/boot/efi.nix | 35 ++++ sys/boot/firmware.nix | 29 +++ sys/boot/fscrypt.nix | 23 +++ sys/boot/impermanence.nix | 53 +++++ sys/boot/namespaced.nix | 27 +++ sys/boot/sb.nix | 37 ++++ sys/boot/stack/btrfs-toplevel-multidrive.nix | 94 +++++++++ sys/boot/stack/default.nix | 6 + sys/boot/stack/luks-ext4-fscrypt-impermanence.nix | 96 +++++++++ sys/btrfs/default.nix | 6 + sys/btrfs/mounts.nix | 39 ++++ sys/btrfs/snapper.nix | 73 +++++++ sys/default.nix | 71 ++----- sys/env/default.nix | 8 + sys/env/domains.nix | 1 + sys/env/maps.nix | 1 + sys/env/users.nix | 1 + sys/env/virtual.nix | 1 + sys/fs/btrfs.nix | 116 ----------- sys/fs/default.nix | 35 ---- sys/fs/layout.nix | 87 -------- sys/gitea/default.nix | 21 ++ sys/hardware/altera.nix | 22 ++ sys/hardware/apc.nix | 30 +++ sys/hardware/bluetooth.nix | 16 ++ sys/hardware/default.nix | 12 ++ sys/hardware/epson.nix | 29 +++ sys/hardware/laptop.nix | 16 ++ sys/hardware/printing.nix | 32 +++ sys/hardware/thinkpad.nix | 38 ++++ sys/hardware/yubico.nix | 14 ++ sys/kiosk/default.nix | 39 ++++ sys/mail/default.nix | 238 ++++++++++++++++++++++ sys/mta/default.nix | 171 ++++++++++++++++ sys/net.nix | 42 ---- sys/net/default.nix | 49 +++++ sys/nspawn.nix | 145 ------------- sys/nspawn/default.nix | 5 + sys/nspawn/dmz.nix | 160 +++++++++++++++ sys/options.nix | 56 ----- sys/preset/default.nix | 6 + sys/preset/dmz.nix | 47 +++++ sys/preset/user.nix | 63 ++++++ sys/seat/default.nix | 85 ++++++++ sys/users.nix | 57 ------ sys/virt/default.nix | 5 + sys/virt/dom/README.md | 1 + sys/virt/libvirt.nix | 53 +++++ sys/web/default.nix | 6 + sys/web/nginx.nix | 45 ++++ sys/web/php-fpm.nix | 152 ++++++++++++++ 60 files changed, 2216 insertions(+), 786 deletions(-) delete mode 100644 sys/auth.nix create mode 100644 sys/auth/default.nix create mode 100644 sys/auth/oath.nix create mode 100644 sys/auth/openssh.nix create mode 100644 sys/baseline/default.nix delete mode 100644 sys/boot.nix create mode 100644 sys/boot/chain.nix create mode 100644 sys/boot/default.nix create mode 100644 sys/boot/detached-luks.nix create mode 100644 sys/boot/efi.nix create mode 100644 sys/boot/firmware.nix create mode 100644 sys/boot/fscrypt.nix create mode 100644 sys/boot/impermanence.nix create mode 100644 sys/boot/namespaced.nix create mode 100644 sys/boot/sb.nix create mode 100644 sys/boot/stack/btrfs-toplevel-multidrive.nix create mode 100644 sys/boot/stack/default.nix create mode 100644 sys/boot/stack/luks-ext4-fscrypt-impermanence.nix create mode 100644 sys/btrfs/default.nix create mode 100644 sys/btrfs/mounts.nix create mode 100644 sys/btrfs/snapper.nix create mode 100644 sys/env/default.nix create mode 100644 sys/env/domains.nix create mode 100644 sys/env/maps.nix create mode 100644 sys/env/users.nix create mode 100644 sys/env/virtual.nix delete mode 100644 sys/fs/btrfs.nix delete mode 100644 sys/fs/default.nix delete mode 100644 sys/fs/layout.nix create mode 100644 sys/gitea/default.nix create mode 100644 sys/hardware/altera.nix create mode 100644 sys/hardware/apc.nix create mode 100644 sys/hardware/bluetooth.nix create mode 100644 sys/hardware/default.nix create mode 100644 sys/hardware/epson.nix create mode 100644 sys/hardware/laptop.nix create mode 100644 sys/hardware/printing.nix create mode 100644 sys/hardware/thinkpad.nix create mode 100644 sys/hardware/yubico.nix create mode 100644 sys/kiosk/default.nix create mode 100644 sys/mail/default.nix create mode 100644 sys/mta/default.nix delete mode 100644 sys/net.nix create mode 100644 sys/net/default.nix delete mode 100644 sys/nspawn.nix create mode 100644 sys/nspawn/default.nix create mode 100644 sys/nspawn/dmz.nix delete mode 100644 sys/options.nix create mode 100644 sys/preset/default.nix create mode 100644 sys/preset/dmz.nix create mode 100644 sys/preset/user.nix create mode 100644 sys/seat/default.nix delete mode 100644 sys/users.nix create mode 100644 sys/virt/default.nix create mode 100644 sys/virt/dom/README.md create mode 100644 sys/virt/libvirt.nix create mode 100644 sys/web/default.nix create mode 100644 sys/web/nginx.nix create mode 100644 sys/web/php-fpm.nix (limited to 'sys') diff --git a/sys/auth.nix b/sys/auth.nix deleted file mode 100644 index 835f836..0000000 --- a/sys/auth.nix +++ /dev/null @@ -1,82 +0,0 @@ -{ config, lib, pkgs, ... }: -with lib; let - cfg = config.local; -in -{ - config = { - security.pam = { - oath = { - usersFile = "/var/trust/auth/users.oath"; - digits = 6; - window = 30; - }; - - services.sshd.oathAuth = true; - }; - - services.openssh = { - enable = true; - openFirewall = false; - ports = [ 2234 ]; - startWhenNeeded = true; - - hostKeys = [ - { - bits = 4096; - path = "/etc/ssh/ssh_host_rsa_key"; - type = "rsa"; - } - { - path = "/etc/ssh/ssh_host_ed25519_key"; - type = "ed25519"; - } - #TODO: Desfasar, inseguro - { - path = "/etc/ssh/ssh_host_ecdsa_key"; - type = "ecdsa"; - } - ]; - - settings = { - X11Forwarding = true; - PermitRootLogin = "no"; - PasswordAuthentication = true; # Necesario para oath, no reemplaza a oath - }; - - extraConfig = '' - # User 'tunnel' has no password. Use PAM OATH - # and connect with -N, forward with -R. - Match User tunnel - AllowTcpForwarding remote - AllowStreamLocalForwarding no - X11Forwarding no - PermitTunnel no - GatewayPorts no - AllowAgentForwarding no - PermitOpen none - PermitListen 60220 60221 60222 60223 60224 60225 60226 60227 60228 60229 - - Banner ${pkgs.writeText "tunnel-banner" '' - This is a reverse tunnel - ''} - ''; - }; - - services.pcscd.enable = true; - services.udev.packages = [ pkgs.yubikey-personalization ]; - - networking.firewall.allowedTCPPorts = [ 2234 ]; - - users.users.tunnel = { - uid = 1100; - group = "nogroup"; - isSystemUser = true; - - # Requiere oath - password = "tunnel"; - - home = "/var/empty"; - shell = "${pkgs.coreutils}/bin/true"; - }; - }; -} diff --git a/sys/auth/default.nix b/sys/auth/default.nix new file mode 100644 index 0000000..4678da9 --- /dev/null +++ b/sys/auth/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./oath.nix + ./openssh.nix + ]; +} diff --git a/sys/auth/oath.nix b/sys/auth/oath.nix new file mode 100644 index 0000000..7030bab --- /dev/null +++ b/sys/auth/oath.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.auth.oath; +in +{ + options.local.auth.oath = { + enable = lib.mkEnableOption "pam-oath"; + }; + + config = lib.mkIf cfg.enable { + security.pam = { + oath = { + digits = 6; + window = 30; + + usersFile = "/var/trust/auth/users.oath"; + }; + + services.sshd.oathAuth = true; + }; + + users.users.tunnel = { + uid = 1100; + group = "nogroup"; + isSystemUser = true; + + # Requiere oath + password = "tunnel"; + + home = "/var/empty"; + shell = "${pkgs.coreutils}/bin/true"; + }; + }; +} diff --git a/sys/auth/openssh.nix b/sys/auth/openssh.nix new file mode 100644 index 0000000..2030682 --- /dev/null +++ b/sys/auth/openssh.nix @@ -0,0 +1,86 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.auth.openssh; + withOath = config.local.auth.oath.enable; +in +{ + options.local.auth.openssh = { + enable = mkEnableOption "openssh"; + tunnel.enable = mkEnableOption "ssh tunnel user"; + + #TODO: Desfasar ecdsa, inseguro + hostKeys = listToAttrs (map + (name: { + inherit name; + + value = mkOption { + type = types.bool; + default = false; + }; + }) [ "ecdsa" "ed25519" "rsa" ]); + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.tunnel.enable -> withOath; + message = "SSH tunnel requires oath"; + } + ]; + + local.boot.impermanence.files = + flatten (map (key: [ key.path "${key.path}.pub" ]) config.services.openssh.hostKeys); + + services.openssh = { + enable = true; + openFirewall = true; + ports = [ 2234 ]; + startWhenNeeded = true; + + hostKeys = map + (name: { + path = "/etc/ssh/ssh_host_${name}_key"; + type = name; + } // optionalAttrs (name == "rsa") { + bits = 4096; + }) + (attrNames (filterAttrs (name: enable: enable) cfg.hostKeys)); + + settings = { + X11Forwarding = true; + PermitRootLogin = "prohibit-password"; + PasswordAuthentication = withOath; # Necesario para oath, no reemplaza a oath + }; + + extraConfig = optionalString cfg.tunnel.enable '' + # User 'tunnel' has no password. Use PAM OATH + # and connect with -N, forward with -R. + Match User tunnel + AllowTcpForwarding remote + AllowStreamLocalForwarding no + X11Forwarding no + PermitTunnel no + GatewayPorts no + AllowAgentForwarding no + PermitOpen none + PermitListen 60220 60221 60222 60223 60224 60225 60226 60227 60228 60229 + + Banner ${pkgs.writeText "tunnel-banner" '' + This is a reverse tunnel + ''} + ''; + }; + + users.users.tunnel = mkIf cfg.tunnel.enable { + uid = 1100; + group = "nogroup"; + isSystemUser = true; + + # Requiere oath + password = "tunnel"; + + home = "/var/empty"; + shell = "${pkgs.coreutils}/bin/true"; + }; + }; +} diff --git a/sys/baseline/default.nix b/sys/baseline/default.nix new file mode 100644 index 0000000..49b9b43 --- /dev/null +++ b/sys/baseline/default.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ... }: +with lib; { + config = { + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "21.11"; # Did you read the comment? + + environment = { + pathsToLink = [ "/share/zsh" ]; + + systemPackages = with pkgs; [ + git + ] ++ optionals (!config.boot.isContainer) [ + lm_sensors + pciutils + smartmontools + usbutils + ]; + }; + + local.boot.impermanence.directories = [ "/var/lib/dhparams" ]; + + nix = { + package = pkgs.nixFlakes; + + extraOptions = '' + experimental-features = nix-command flakes repl-flake + ''; + + # No me interesa el global registry + settings.flake-registry = ""; + }; + + programs = { + fuse.userAllowOther = true; + zsh.enable = true; + }; + + security.dhparams = { + enable = true; + defaultBitSize = 4096; + }; + + services.earlyoom = { + enable = mkDefault true; + enableNotifications = true; + }; + + # Coredumps son un riesgo de seguridad y puden usar mucho disco + systemd.coredump.extraConfig = '' + Storage=none + ProcessSizeMax=0 + ''; + + time.timeZone = mkDefault "America/Costa_Rica"; + }; +} diff --git a/sys/boot.nix b/sys/boot.nix deleted file mode 100644 index 1e8685a..0000000 --- a/sys/boot.nix +++ /dev/null @@ -1,116 +0,0 @@ -{ lib, config, pkgs, ... }: -with lib; let - cfg = config.local; -in -{ - options.local = with lib.types; { - loader = mkOption { - type = enum [ "grub" "systemd-boot" ]; - }; - - cpuVendor = mkOption { - type = enum [ "amd" "intel" ]; - }; - - canTouchEfiVariables = mkOption { - type = bool; - }; - - videoDrivers = mkOption { - type = listOf str; - }; - - initrdModules = mkOption { - type = listOf str; - }; - }; - - config = { - boot = { - kernelPackages = pkgs.linuxPackages_latest; - - loader = (if cfg.loader == "grub" then { - grub = { - enable = true; - device = "nodev"; - efiSupport = true; - }; - } else { - systemd-boot.enable = true; - }) // { - efi = { - inherit (cfg) canTouchEfiVariables; - }; - }; - - initrd = - let - crypt = cfg.crypt.toplevel; - headerPathEscaped = escapeShellArg "/initrd-boot/${crypt.headerFromBoot}"; - in - { - availableKernelModules = cfg.initrdModules; - supportedFilesystems = [ "vfat" ]; - - preDeviceCommands = optionalString (crypt != null) '' - mkdir -p `dirname ${headerPathEscaped}` - touch ${headerPathEscaped} - ''; - - preLVMCommands = optionalString cfg.portable '' - sleep 2 #TODO - ''; - - postMountCommands = - let - fromRoot = path: escapeShellArg "/mnt-root/${path}"; - auxOpen = aux: '' - cryptsetup -v open \ - --header ${fromRoot aux.header} \ - --key-file ${fromRoot aux.keyfile} \ - ${aux.device} ${aux.target} - ''; - in - concatStringsSep "\n" (map auxOpen cfg.crypt.aux); - - luks.devices = mkIf (crypt != null) { - "${crypt.target}" = { - inherit (crypt) device; - header = "/initrd-boot/${crypt.headerFromBoot}"; - preLVM = false; - - preOpenCommands = '' - mount -o ro -t vfat ${escapeShellArg cfg.fs.boot.device} /initrd-boot - ''; - - postOpenCommands = '' - umount /initrd-boot - ''; - }; - }; - - #network = { - # enable = true; - - # ssh = { - # enable = true; - # port = 2234; - # }; - #}; - }; - }; - - hardware = { - cpu = - let - ucode.updateMicrocode = true; - in - { - amd = mkIf (cfg.cpuVendor == "amd") ucode; - intel = mkIf (cfg.cpuVendor == "intel") ucode; - }; - - enableRedistributableFirmware = true; - }; - }; -} diff --git a/sys/boot/chain.nix b/sys/boot/chain.nix new file mode 100644 index 0000000..c726cf8 --- /dev/null +++ b/sys/boot/chain.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot; +in +{ + options.local.boot = { + enable = mkEnableOption "system boot"; + + loader = mkOption { + type = types.enum [ "grub" "systemd-boot" ]; + }; + + kernel = mkOption { + type = types.raw; + }; + }; + + config = mkIf cfg.enable { + boot = { + kernelPackages = cfg.kernel; + + loader = + if cfg.loader == "grub" then { + grub = { + enable = true; + device = "nodev"; + efiSupport = true; + }; + } else { + systemd-boot = { + enable = true; + editor = true; + }; + }; + }; + }; +} diff --git a/sys/boot/default.nix b/sys/boot/default.nix new file mode 100644 index 0000000..157ba0e --- /dev/null +++ b/sys/boot/default.nix @@ -0,0 +1,13 @@ +{ + imports = [ + ./chain.nix + ./detached-luks.nix + ./efi.nix + ./firmware.nix + ./fscrypt.nix + ./impermanence.nix + ./namespaced.nix + ./sb.nix + ./stack + ]; +} diff --git a/sys/boot/detached-luks.nix b/sys/boot/detached-luks.nix new file mode 100644 index 0000000..a7b1bc9 --- /dev/null +++ b/sys/boot/detached-luks.nix @@ -0,0 +1,73 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.detachedLuks; +in +{ + options.local.boot.detachedLuks = { + enable = mkEnableOption "detached LUKS header in initrd"; + + headerFromBoot = mkOption { + type = types.str; + }; + + crypt = mkOption { + type = types.str; + }; + + target = mkOption { + type = types.str; + }; + }; + + config = mkIf cfg.enable { + boot.initrd = + let + headerPath = "/initrd-boot/${cfg.headerFromBoot}"; + in + { + preDeviceCommands = + let + headerPathEscaped = escapeShellArg headerPath; + in + '' + mkdir -p `dirname ${headerPathEscaped}` + touch ${headerPathEscaped} + ''; + + postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) '' + # Set the system time from the hardware clock to work around a + # bug in qemu-kvm > 1.5.2 (where the VM clock is initialised + # to the *boot time* of the host). + hwclock -s + ''; + + #FIXME: Demasiado vulgar + preLVMCommands = optionalString (config.local.boot.efi.enable && config.local.boot.efi.removable) '' + sleep 2 + ''; + + luks.devices.${cfg.target} = { + device = cfg.crypt; + header = headerPath; + preLVM = false; + + preOpenCommands = + let + boot = config.fileSystems."/boot"; + in + '' + mount -o ro -t ${boot.fsType} ${boot.device} /initrd-boot + ''; + + postOpenCommands = mkBefore '' + umount /initrd-boot + ''; + }; + }; + + local.boot.stack = { + btrfsToplevelMultidrive.toplevel.device = "/dev/mapper/${cfg.target}"; + luksExt4FscryptImpermanence = { inherit (cfg) target; }; + }; + }; +} diff --git a/sys/boot/efi.nix b/sys/boot/efi.nix new file mode 100644 index 0000000..35cf687 --- /dev/null +++ b/sys/boot/efi.nix @@ -0,0 +1,35 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.boot.efi; +in +{ + options.local.boot.efi = { + enable = mkEnableOption "EFI with FAT32 system partition"; + + esp.uuid = mkOption { + type = types.strMatching "[0-9A-F]{4}-[0-9A-F]{4}"; + }; + + removable = mkOption { + type = types.bool; + }; + }; + + config = mkIf cfg.enable { + boot = { + initrd.supportedFilesystems = [ "vfat" ]; + + loader = { + efi.canTouchEfiVariables = !cfg.removable; + grub.efiInstallAsRemovable = cfg.removable; + }; + }; + + fileSystems."/boot" = { + device = "/dev/disk/by-uuid/${cfg.esp.uuid}"; + fsType = "vfat"; + options = [ "noatime" "umask=027" "sync" ]; + neededForBoot = true; + }; + }; +} diff --git a/sys/boot/firmware.nix b/sys/boot/firmware.nix new file mode 100644 index 0000000..70a3c4b --- /dev/null +++ b/sys/boot/firmware.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.firmware; +in +{ + options.local.boot.firmware = { + mode = mkOption { + type = types.enum [ "none" "redistributable" "all" ]; + }; + + cpuVendor = mkOption { + type = types.enum [ "amd" "intel" ]; + }; + }; + + config = mkIf (cfg.mode != "none") { + hardware = { + cpu = { + amd.updateMicrocode = cfg.cpuVendor == "amd"; + intel.updateMicrocode = cfg.cpuVendor == "intel"; + }; + + enableAllFirmware = cfg.mode == "all"; + enableRedistributableFirmware = true; + }; + + services.fwupd.enable = true; + }; +} diff --git a/sys/boot/fscrypt.nix b/sys/boot/fscrypt.nix new file mode 100644 index 0000000..e6a745c --- /dev/null +++ b/sys/boot/fscrypt.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.fscrypt; +in +{ + options.local.boot.fscrypt = { + enable = mkEnableOption "fscrypt support"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.fscrypt-experimental ]; + + local.boot.impermanence = { + directories = [ + { directory = "/.fscrypt"; mode = "u=rwx,g=rx,o=rx"; } + ]; + + files = [ + "/etc/fscrypt.conf" + ]; + }; + }; +} diff --git a/sys/boot/impermanence.nix b/sys/boot/impermanence.nix new file mode 100644 index 0000000..4902239 --- /dev/null +++ b/sys/boot/impermanence.nix @@ -0,0 +1,53 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.boot.impermanence; +in +{ + options.local.boot.impermanence = { + enable = mkEnableOption "root fs impermanence"; + + #TODO: type correcto de files, directories? + + directories = mkOption { + type = with lib.types; listOf (either str attrs); + default = [ ]; + }; + + files = mkOption { + type = with lib.types; listOf (either str attrs); + default = [ ]; + }; + }; + + config = mkMerge [ + { + local.boot.impermanence = { + directories = [ + "/etc/lvm" + "/var/lib/nixos" + "/var/log" + ]; + + files = [ + "/etc/machine-id" + "/var/lib/logrotate.status" + ]; + }; + } + (mkIf cfg.enable { + assertions = [ + { + assertion = (config.fileSystems ? "/persist") && config.fileSystems."/persist".neededForBoot; + message = "Impermanence requires /persist to be a neededForBoot mountpoint"; + } + ]; + + environment.persistence."/persist" = { + hideMounts = true; + + files = cfg.files; + directories = cfg.directories; + }; + }) + ]; +} diff --git a/sys/boot/namespaced.nix b/sys/boot/namespaced.nix new file mode 100644 index 0000000..9927ae2 --- /dev/null +++ b/sys/boot/namespaced.nix @@ -0,0 +1,27 @@ +{ config, lib, options, ... }: +with lib; let + cfg = config.local.boot.namespaced; +in +{ + options.local.boot.namespaced = { + enable = mkEnableOption "system containerization"; + }; + + config = mkIf cfg.enable { + boot.isContainer = true; + + local.boot = mkMerge ([ + { + enable = mkForce false; + + efi.enable = mkForce false; + secureBoot.enable = mkForce false; + impermanence.enable = mkForce false; + } + ] ++ map + (name: { + stack.${name}.enable = mkForce false; + }) + (attrNames options.local.boot.stack)); + }; +} diff --git a/sys/boot/sb.nix b/sys/boot/sb.nix new file mode 100644 index 0000000..bdf7f0f --- /dev/null +++ b/sys/boot/sb.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.secureBoot; +in +{ + options.local.boot.secureBoot = { + enable = mkEnableOption "secure boot"; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = config.local.boot.efi.enable; + message = "secure boot requires EFI"; + } + { + assertion = config.local.boot.loader == "systemd-boot"; + message = "lanzaboote requires systemd-boot"; + } + ]; + + boot = { + loader.systemd-boot.enable = mkForce false; + + lanzaboote = { + enable = true; + pkiBundle = "/etc/secureboot"; + }; + }; + + environment.systemPackages = [ + pkgs.sbctl + ]; + + local.boot.impermanence.directories = [ "/etc/secureboot" ]; + }; +} diff --git a/sys/boot/stack/btrfs-toplevel-multidrive.nix b/sys/boot/stack/btrfs-toplevel-multidrive.nix new file mode 100644 index 0000000..1dbfa14 --- /dev/null +++ b/sys/boot/stack/btrfs-toplevel-multidrive.nix @@ -0,0 +1,94 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.boot.stack.btrfsToplevelMultidrive; +in +{ + options.local.boot.stack.btrfsToplevelMultidrive = { + enable = mkEnableOption "filesystem stack: persistent btrfs toplevel with optional hdd drive"; + + toplevel = { + device = mkOption { + type = types.str; + }; + + ssd = mkOption { + type = types.bool; + }; + + snapshot = mkOption { + type = types.bool; + default = false; + }; + + root = mkOption { + type = types.str; + }; + + pivot = mkOption { + type = types.str; + default = "/"; + }; + }; + + secondary = { + device = mkOption { + type = types.str; + }; + + ssd = mkOption { + type = types.bool; + }; + + snapshot = mkOption { + type = types.bool; + default = false; + }; + + home = mkOption { + type = types.str; + }; + + pivot = mkOption { + type = types.str; + default = "/"; + }; + }; + }; + + config = mkIf cfg.enable { + local.btrfs = { + mounts = { + "/" = { + inherit (cfg.toplevel) device ssd; + subvol = cfg.toplevel.root; + }; + + "/toplevel" = { + inherit (cfg.toplevel) device ssd; + subvol = cfg.toplevel.pivot; + }; + + #FIXME: Este nombre es legacy + "/hdd" = { + inherit (cfg.secondary) device ssd; + subvol = cfg.secondary.pivot; + }; + + "/home" = { + inherit (cfg.secondary) device ssd; + subvol = cfg.secondary.home; + }; + }; + + snapper = optionalAttrs cfg.toplevel.snapshot + { + root = "/"; + } // optionalAttrs cfg.secondary.snapshot { + home = "/home"; + }; + }; + + # Asegura que /hdd sea descifrado antes de intentar montar /home + fileSystems."/home".depends = [ "/hdd" ]; + }; +} diff --git a/sys/boot/stack/default.nix b/sys/boot/stack/default.nix new file mode 100644 index 0000000..ff211e6 --- /dev/null +++ b/sys/boot/stack/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./btrfs-toplevel-multidrive.nix + ./luks-ext4-fscrypt-impermanence.nix + ]; +} diff --git a/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix new file mode 100644 index 0000000..72336d6 --- /dev/null +++ b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix @@ -0,0 +1,96 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.stack.luksExt4FscryptImpermanence; +in +{ + options.local.boot.stack.luksExt4FscryptImpermanence = { + enable = mkEnableOption "filesystem stack: whatever LUKS approach+ext4+impermanence with per-boot keys"; + + target = mkOption { + type = types.str; + }; + }; + + # - boot device + # - some unknown fs, probably vfat + # - detached luks header file + # + # - toplevel device + # - headerless luks + # - /toplevel (ext4) + # - /toplevel/nix + # - /toplevel/persist + # - /toplevel/boot-archive.pub + # - /toplevel/boot-keys + # - /toplevel/boot-keys/2000-01-01T00:00:00-06:00.key.crypt (encrypted for /toplevel/boot-archive.pub) + # - /toplevel/boot-keys/... + # - /toplevel/boot-keys/last.key.crypt -> 2000-01-01T00:00:00-06:00.key.crypt + # - /toplevel/boots + # - /toplevel/boots/2000-01-01T00:00:00-06:00 (raw protector in last.key.crypt) + # - /toplevel/boots/... + # - /toplevel/boots/last -> 2000-01-01T00:00:00-06:00 (mounted as /) + config = mkIf cfg.enable { + boot.initrd.luks.devices.${cfg.target}.postOpenCommands = + let + fscryptctl = "${pkgs.fscryptctl}/bin/fscryptctl"; + in + '' + # FIXME: posiblemente algunos --make-* son innecesarios a partir de aquí + mkdir -p /mnt-root /mnt-toplevel + mount -o noatime /dev/mapper/${cfg.target} /mnt-toplevel + mount --make-private /mnt-toplevel + + boot_stamp="$(date -Is)" + root_from_toplevel="/mnt-toplevel/boots/$boot_stamp" + + mkdir -p "$root_from_toplevel" /mnt-toplevel/boot-keys + chmod 700 /mnt-toplevel/boot-keys + + head -c64 /dev/urandom >/boot-key + key_id=$(${fscryptctl} add_key /mnt-toplevel Power -> Sleep State to "Linux" in EFI. + # See https://wiki.archlinux.org/index.php/Lenovo_ThinkPad_X1_Carbon_(Gen_6)#Suspend_issues + # Fingerprint sensor requires a firmware-update to work. + + boot = { + extraModulePackages = with config.boot.kernelPackages; [ acpi_call digimend ]; + extraModprobeConfig = "options iwlwifi 11n_disable=1 wd_disable=1"; + + # acpi_call makes tlp work for newer thinkpads + kernelModules = [ "acpi_call" "digimend" ]; + + # Force use of the thinkpad_acpi driver for backlight control. + # This allows the backlight save/load systemd service to work. + kernelParams = [ "acpi_backlight=native" ]; + }; + + hardware.firmware = [ pkgs.sof-firmware ]; + + local.hardware.laptop.enable = true; + + services = { + fprintd.enable = true; + thinkfan.enable = true; + tlp.enable = true; + tp-auto-kbbl.enable = true; + }; + }; +} diff --git a/sys/hardware/yubico.nix b/sys/hardware/yubico.nix new file mode 100644 index 0000000..a3440c3 --- /dev/null +++ b/sys/hardware/yubico.nix @@ -0,0 +1,14 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.hardware.yubico; +in +{ + options.local.hardware.yubico = { + enable = mkEnableOption "Yubico hardware support"; + }; + + config = mkIf cfg.enable { + services.pcscd.enable = true; + services.udev.packages = [ pkgs.yubikey-personalization ]; + }; +} diff --git a/sys/kiosk/default.nix b/sys/kiosk/default.nix new file mode 100644 index 0000000..b450733 --- /dev/null +++ b/sys/kiosk/default.nix @@ -0,0 +1,39 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.kiosk; +in +{ + options.local.kiosk = { + enable = mkEnableOption "kiosk mode"; + + program = mkOption { + type = types.str; + }; + + user = mkOption { + type = types.str; + }; + + allowVTSwitch = mkOption { + type = types.bool; + default = false; + }; + }; + + config = mkIf cfg.enable { + services = { + cage = { + enable = true; + inherit (cfg) program user; + + extraArguments = [ (if cfg.allowVTSwitch then "-sd" else "-d") ]; + }; + + physlock = { + enable = true; + disableSysRq = true; + muteKernelMessages = true; + }; + }; + }; +} diff --git a/sys/mail/default.nix b/sys/mail/default.nix new file mode 100644 index 0000000..5b7e4b5 --- /dev/null +++ b/sys/mail/default.nix @@ -0,0 +1,238 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.mailHost; + imapHostname = config.local.domains.imap.main; + + inherit (config.local) users virtual; +in +{ + options.local.mailHost = { + enable = mkEnableOption "mailbox host service"; + + mdaListen = mkOption { + type = types.str; + }; + + saslPort = mkOption { + type = types.port; + }; + + lmtpPort = mkOption { + type = types.port; + }; + }; + + config = mkIf cfg.enable { + services.dovecot2 = + let + cert = config.security.acme.certs.${imapHostname}.directory; + in + { + enable = true; + enablePAM = false; + enableLmtp = true; + + sslServerKey = "${cert}/key.pem"; + sslServerCert = "${cert}/fullchain.pem"; + + modules = [ pkgs.dovecot_pigeonhole ]; + + mailUser = "vmail"; + mailGroup = "vmail"; + mailLocation = "maildir:~/mail"; + mailPlugins.perProtocol.lmtp.enable = [ "sieve" ]; + + extraConfig = + let + inherit (config.networking) domain; + + # https://dovecot.org/list/dovecot/2019-March/115250.html + # Otra solución posible (https://serverfault.com/a/1062274/980378): + # auth_username_format = %{if;%d;eq;${domain};%Ln;%Lu} + localEntry = canonical: username: '' + ${username}:::::::user=${canonical} nopassword userdb_user=${canonical} + ''; + + localMailboxes = + pkgs.writeText "local-mailboxes" + (concatStrings + (flatten (mapAttrsToList + (canonical: user: + map (localEntry canonical) ([ canonical ] ++ user.hardAliases)) + users))); + + localCerts = + flatten (mapAttrsToList + (canonical: user: + let + certNames = { + inherit canonical; + logins = [ canonical ] ++ user.hardAliases; + }; + in + map (flip nameValuePair certNames) user.mail.certs) + users); + + vmailCerts = + flatten (flatten (mapAttrsToList + (domain: virtual: mapAttrsToList + (username: user: + let + address = "${username}@${domain}"; + + certNames = { + canonical = address; + logins = [ address ]; + }; + in + map (flip nameValuePair certNames) user.mail.certs) + virtual.users) + virtual)); + + certLogins = + pkgs.writeText "cert-logins" + (concatStrings (flatten (mapAttrsToList + (uuid: names: map + (addr: '' + ${uuid}.mail-client@nodomain,${addr}:::::::user=${names.canonical} + '') + names.logins) + (listToAttrs (localCerts ++ vmailCerts))))); + + vmailPath = "/var/lib/vmail/%{if;%d;ne;;%Ld;${domain}}"; + in + '' + auth_mechanisms = plain login external + + #TODO: automatizar implantación de archivo de CA + + # Orden de concatenación de mail-fullchain-crl.crt: + # - Issuing CA cert + # - Issuing CA CRL + # - Intermediate CA cert + # - Intermediate CA CRL + # - Root CA cert + # - Root CA CRL + ssl_ca = +# See also: +# - +# - + +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.services.php-fpm-isolated; + + configFile = { pool, poolOpts, runtimeDir, sockFile, pidFile }: + let + config = { + global = { + daemonize = false; + error_log = "syslog"; + pid = pidFile; + }; + + "${pool}" = + let + enforced = { + inherit (poolOpts) user group; + listen = sockFile; + }; + + defaults = { + "pm" = "dynamic"; + "pm.max_children" = 16; + "pm.min_spare_servers" = 1; + "pm.max_spare_servers" = 4; + "pm.start_servers" = 1; + "catch_workers_output" = true; + "php_admin_flag[log_errors]" = true; + "env[PATH]" = makeBinPath [ pkgs.php ]; + }; + + env = mapAttrs' + (name: value: { + name = "env[${name}]"; + value = "\"${escape [ "\"" ] value}\""; + }) + poolOpts.env; + in + defaults // poolOpts.config // env // enforced; + }; + in + (pkgs.formats.ini { }).generate "php-fpm-pool-${pool}.conf" config; +in +{ + options.services.php-fpm-isolated.pools = mkOption { + default = { }; + + type = with types; attrsOf (submodule { + options = { + enable = mkEnableOption "PHP-FPM pool"; + + user = mkOption { + type = str; + }; + + group = mkOption { + type = str; + }; + + unveil = mkOption { + type = listOf (either package str); + }; + + env = mkOption { + type = attrsOf str; + default = { }; + }; + + config = mkOption { + type = attrsOf (oneOf [ int str bool ]); + default = { }; + }; + }; + }); + }; + + config.systemd = + let + php-fpm = "${pkgs.php}/bin/php-fpm"; + + unitsFor = pool: poolOpts: + let + runtimeBase = "php-fpm-isolated/${pool}"; + runtimeDir = "/run/${runtimeBase}"; + pidFile = "${runtimeDir}/${pool}.pid"; + sockFile = "${runtimeDir}/${pool}.sock"; + in + { + name = "php-fpm-pool-${pool}"; + + value.service = { + description = "PHP-FPM process manager for pool '${pool}'"; + after = [ "network.target" ]; + + confinement.enable = true; + + serviceConfig = { + Type = "notify"; + ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"; + PIDFile = pidFile; + + Environment = "FPM_SOCKETS=${sockFile}=3"; + + ExecStart = + let + fpmConfig = configFile { + inherit pool poolOpts runtimeDir sockFile pidFile; + }; + in + "${php-fpm} --nodaemonize --fpm-config ${fpmConfig} --pid ${pidFile}"; + + PrivateTmp = true; + PrivateNetwork = true; + PrivateDevices = true; + # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; + + User = poolOpts.user; + Group = poolOpts.group; + RuntimeDirectory = runtimeBase; + + BindReadOnlyPaths = + let + unveiled = map builtins.toString poolOpts.unveil; + in + [ "/run/systemd/journal/socket" ] ++ unveiled; + }; + }; + + value.socket = { + description = "PHP-FPM socket for pool '${pool}'"; + listenStreams = [ sockFile ]; + + socketConfig = { + User = poolOpts.user; + Group = poolOpts.group; + }; + }; + }; + + units = mapAttrs' unitsFor (filterAttrs (_: pool: pool.enable) cfg.pools); + in + { + sockets = mapAttrs (_: unit: unit.socket) units; + services = mapAttrs (_: unit: unit.service) units; + }; +} -- cgit v1.2.3