From 84ac34cb6c1c9f9bb20065551b72c57469c0c4bc Mon Sep 17 00:00:00 2001 From: oddlama Date: Thu, 29 Jun 2023 00:27:54 +0200 Subject: [PATCH] refactor: major refactor into proper reusable modules. No logical changes. --- README.md | 85 ++++++----- {nix/apps => apps}/default.nix | 1 - {nix/apps => apps}/format-secrets.nix | 0 {nix/apps => apps}/show-wireguard-qr.nix | 4 +- flake.nix | 4 +- hosts/common/bios-boot.nix | 10 -- hosts/common/core/default.nix | 44 ------ hosts/common/core/net.nix | 92 ------------ hosts/common/efi.nix | 11 -- hosts/nom/default.nix | 26 ++-- hosts/sentinel/acme.nix | 2 +- hosts/sentinel/default.nix | 22 +-- hosts/sentinel/net.nix | 2 +- hosts/sentinel/oauth2.nix | 10 +- hosts/ward/default.nix | 26 ++-- hosts/ward/microvms/adguardhome/default.nix | 28 +--- hosts/ward/microvms/common.nix | 18 +++ hosts/ward/microvms/grafana/default.nix | 38 ++--- hosts/ward/microvms/influxdb/default.nix | 31 +--- hosts/ward/microvms/kanidm/default.nix | 28 +--- hosts/ward/microvms/loki/default.nix | 28 +--- hosts/ward/microvms/vaultwarden/default.nix | 88 +++++------- hosts/ward/net.nix | 4 +- hosts/zackbiene/default.nix | 15 +- hosts/zackbiene/hostapd.nix | 3 - modules/config/boot.nix | 23 +++ modules/config/home-manager.nix | 12 ++ .../core => modules/config}/impermanence.nix | 0 .../core => modules/config}/inputrc.nix | 0 .../common/core => modules/config}/issue.nix | 0 .../core/system.nix => modules/config/lib.nix | 112 +-------------- modules/config/microvms.nix | 8 ++ modules/config/net.nix | 20 +++ modules/config/nftables.nix | 70 +++++++++ {hosts/common/core => modules/config}/nix.nix | 0 .../core => modules/config}/resolved.nix | 4 +- modules/config/secrets.nix | 64 +++++++++ {hosts/common/core => modules/config}/ssh.nix | 0 modules/config/system.nix | 10 ++ modules/config/users.nix | 27 ++++ {hosts/common/core => modules/config}/xdg.nix | 0 modules/default.nix | 42 ++++++ modules/extra.nix | 136 ------------------ modules/{ => meta}/microvms.nix | 16 +-- modules/meta/nginx.nix | 79 ++++++++++ modules/{ => meta}/oauth2-proxy.nix | 6 +- modules/{ => meta}/promtail.nix | 6 +- modules/{ => meta}/telegraf.nix | 15 +- modules/meta/wireguard-proxy.nix | 81 +++++++++++ modules/{ => meta}/wireguard.nix | 4 +- modules/{ => networking}/hostapd.nix | 1 + modules/{ => networking}/interface-naming.nix | 5 +- modules/{ => networking}/provided-domains.nix | 2 +- modules/optional/boot-bios.nix | 7 + modules/optional/boot-efi.nix | 7 + .../optional}/dev/default.nix | 1 + .../optional}/dev/documentation.nix | 0 .../optional/dev}/yubikey.nix | 0 .../optional}/graphical/default.nix | 0 .../optional}/graphical/fonts.nix | 0 .../optional}/graphical/wayland.nix | 0 .../optional}/hardware/bluetooth.nix | 0 .../optional}/hardware/hetzner-cloud.nix | 0 .../optional}/hardware/intel.nix | 0 .../optional}/hardware/nvidia.nix | 3 - .../optional}/hardware/odroid-n2plus.nix | 0 .../optional}/hardware/physical.nix | 0 .../optional}/initrd-ssh.nix | 0 {hosts/common => modules/optional}/laptop.nix | 0 {hosts/common => modules/optional}/sound.nix | 0 {hosts/common => modules/optional}/zfs.nix | 0 modules/proxy-via-sentinel.nix | 25 ---- modules/{ => repo}/distributed-config.nix | 3 +- modules/repo/meta.nix | 19 +++ modules/{repo.nix => repo/secrets.nix} | 2 +- modules/security/acme-wildcard.nix | 59 ++++++++ modules/{ => system}/deteministic-ids.nix | 0 nix/apps/draw-graph.nix | 39 ----- nix/checks.nix | 3 +- nix/lib.nix | 6 +- 80 files changed, 761 insertions(+), 776 deletions(-) rename {nix/apps => apps}/default.nix (95%) rename {nix/apps => apps}/format-secrets.nix (100%) rename {nix/apps => apps}/show-wireguard-qr.nix (92%) delete mode 100644 hosts/common/bios-boot.nix delete mode 100644 hosts/common/core/default.nix delete mode 100644 hosts/common/core/net.nix delete mode 100644 hosts/common/efi.nix create mode 100644 hosts/ward/microvms/common.nix create mode 100644 modules/config/boot.nix create mode 100644 modules/config/home-manager.nix rename {hosts/common/core => modules/config}/impermanence.nix (100%) rename {hosts/common/core => modules/config}/inputrc.nix (100%) rename {hosts/common/core => modules/config}/issue.nix (100%) rename hosts/common/core/system.nix => modules/config/lib.nix (81%) create mode 100644 modules/config/microvms.nix create mode 100644 modules/config/net.nix create mode 100644 modules/config/nftables.nix rename {hosts/common/core => modules/config}/nix.nix (100%) rename {hosts/common/core => modules/config}/resolved.nix (96%) create mode 100644 modules/config/secrets.nix rename {hosts/common/core => modules/config}/ssh.nix (100%) create mode 100644 modules/config/system.nix create mode 100644 modules/config/users.nix rename {hosts/common/core => modules/config}/xdg.nix (100%) create mode 100644 modules/default.nix delete mode 100644 modules/extra.nix rename modules/{ => meta}/microvms.nix (96%) create mode 100644 modules/meta/nginx.nix rename modules/{ => meta}/oauth2-proxy.nix (96%) rename modules/{ => meta}/promtail.nix (96%) rename modules/{ => meta}/telegraf.nix (90%) create mode 100644 modules/meta/wireguard-proxy.nix rename modules/{ => meta}/wireguard.nix (99%) rename modules/{ => networking}/hostapd.nix (99%) rename modules/{ => networking}/interface-naming.nix (86%) rename modules/{ => networking}/provided-domains.nix (81%) create mode 100644 modules/optional/boot-bios.nix create mode 100644 modules/optional/boot-efi.nix rename {hosts/common => modules/optional}/dev/default.nix (87%) rename {hosts/common => modules/optional}/dev/documentation.nix (100%) rename {hosts/common => modules/optional/dev}/yubikey.nix (100%) rename {hosts/common => modules/optional}/graphical/default.nix (100%) rename {hosts/common => modules/optional}/graphical/fonts.nix (100%) rename {hosts/common => modules/optional}/graphical/wayland.nix (100%) rename {hosts/common => modules/optional}/hardware/bluetooth.nix (100%) rename {hosts/common => modules/optional}/hardware/hetzner-cloud.nix (100%) rename {hosts/common => modules/optional}/hardware/intel.nix (100%) rename {hosts/common => modules/optional}/hardware/nvidia.nix (75%) rename {hosts/common => modules/optional}/hardware/odroid-n2plus.nix (100%) rename {hosts/common => modules/optional}/hardware/physical.nix (100%) rename {hosts/common => modules/optional}/initrd-ssh.nix (100%) rename {hosts/common => modules/optional}/laptop.nix (100%) rename {hosts/common => modules/optional}/sound.nix (100%) rename {hosts/common => modules/optional}/zfs.nix (100%) delete mode 100644 modules/proxy-via-sentinel.nix rename modules/{ => repo}/distributed-config.nix (91%) create mode 100644 modules/repo/meta.nix rename modules/{repo.nix => repo/secrets.nix} (97%) create mode 100644 modules/security/acme-wildcard.nix rename modules/{ => system}/deteministic-ids.nix (100%) delete mode 100644 nix/apps/draw-graph.nix diff --git a/README.md b/README.md index 6d6fe57..b1bd042 100644 --- a/README.md +++ b/README.md @@ -2,39 +2,64 @@ This is my personal nix config. -## Structure +## Hosts -- `hosts/` contains configuration for all hosts. - - `common/` shared configuration. Hosts will include what they need from here. - - `core/` configuration that is shared across _all_ machines. (base setup, ssh, ...) - - `dev/` configuration for development machines - - `graphical/` configuration for graphical setup - - `hardware/` configuration for various hardware components - - `.nix` commonly required configuration for `` - - `/` configuration for `` - - `[microvms/]` configuration for microvms. This is optional even for existing microvms, since they can also be defined in-place. - - `secrets/` Local secrets for this host. Still theoretically accessible by other hosts, but owned by this one. - - `local.nix.age` Repository-wide local secrets. Decrypted on import via `builtins.extraBuiltins.rageImportEncrypted`. - - `[host.pub]` This host's public key. Used for agenix rekeying if it exists. - - `default.nix` The actual system definition. Follow the imports from there to see what it entails. - - `fs.nix` Filesystem setup. - - `net.nix` Networking setup. + TODO make a table. - `nom/` - My laptop and main development machine - `ward/` - ODROID H3, energy efficient SBC. Used as a firewall betwenn my ISP and internal home network. Hosts some lightweight services using full KVM virtual machines. - `envoy/` - Hetzner Cloud server. Primarily used as my mailserver and VPN provider. + - `sentinel/` - Hetzner Cloud server. Primarily used as a http proxy - `zackbiene/` - ODROID N2+. Hosts IoT and Home Automation stuff and fully isolates that stuff from my internal network. - not yet ready to be publicized: my main development machine, the powerful home server, some services ... (still in transition from gentoo :/) -- `modules/` additional NixOS modules that are not yet upstreamed, or specific to this setup. - - `interface-naming.nix` Provides an option to rename interfaces based on their MAC address - - `microvms.nix` Used to define microvms including all of the boilerplate setup (networking, shares, local wireguard) - - `repo.nix` Provides options to define and access repository-wide secrets - - `wireguard.nix` A meta module that allows defining wireguard networks that automatically collects network participants across nodes -- `nix/` library functions and plumbing - - `apps/` Additional runnable actions for this flake - - `default.nix` Collects all apps and generates a definition for a specified system - - `draw-graph.nix` (**WIP:** infrastructure graph renderer) - - `format-secrets.nix` Runs the code formatter on the secret .nix files - - `show-wireguard-qr.nix` Generates a QR code for external wireguard participants + +## Structure + +- `apps/` Additional runnable actions for flake maintenance, like showing wireguard QR codes. + +- `hosts/` contains the top-level configuration for ``. + Follow the imports from there to see what it entails. + + By convention I place secrets related to this host in the `secrets/` subfolder, but any host + could technically use them. Especialy important files in this folder are: + + - `host.pub` This host's public key (retrieved after initial setup). Used to rekey secrets so the host can access them at runtime. + - `local.nix.age` Repository-wide local secrets. Decrypted on import, see `modules/repo/secrets.nix` for more information. + + Some hosts define microvms that run as their guests. These are typically stored + in `microvms/` and have the same layout as a regular host. + +- `modules/` contains modularized configuration. If you are interested in reusable parts of + my configuration, this is probably the folder you are looking for. Unless stated otherwise, + all of these will be regular reusable modules like those you would find in `nixpkgs/nixos/modules`, + and the tree of all relevant modules is included via `modules/default.nix`. + + - `modules/config/` contains configuration that is I use across all my host and is applied by default. + These just add configuration unconditionally and don't expose any further options. + + - `modules/optional/` contains configuration that is only needed sometimes, and which should + be included explicitly by hosts that require it. + + - `modules/meta/` contains meta-modules that simplify the option interface of existing options. + I use this for stuff that I don't need on all my hosts and that may require different settings + for each host while sharing a common basis. + + Some of these are "meta" in the sense that they depend on their own definitions on multiple hosts (wireguard). + These are probably as opinionated as stuff in `modules/config/` but may be a little more general. + The `wireguard` module would even be a candidate for extraction to a separate flake, together with the related apps. + + - `modules//` regular modules related to , similar structure as in `nixpkgs/nixos/modules` + +- `pkgs/` Custom packages and scripts + +- `secrets/` Global secrets and age identities + - `global.nix.age` Repository-wide global secrets. Available on nodes via the repo module as `config.repo.secrets.global`. + - `backup.pub` Backup age-identity in case I ever lose my YubiKey or it breaks. + - `yk1-nix-rage.pub` Master YubiKey split-identity. Used as a key-grab. + +- `users/` User account configuration mostly via home-manager. + This is the place to look for my dotfiles. + +- `nix/` library functions and flake plumbing - `checks.nix` pre-commit-hooks for this repository - `colmena.nix` Setup for distributed deployment using colmena (actually defines all NixOS hosts) - `dev-shell.nix` Environment setup for `nix develop` for using this flake @@ -43,12 +68,6 @@ This is my personal nix config. - `generate-node.nix` Helper function that outputs everything that is necessary to define a new node in a predictable format. Used to define colmena nodes and microvms. - `lib.nix` Commonly used functionality or helpers that weren't available in the standard library - `rage-decrypt-and-cache.sh` Auxiliary script for repository-wide secrets that decrypts a file and caches the output in /tmp -- `secrets/` Global secrets and age identities - - `global.nix.age` Repository-wide global secrets. Available on nodes via the repo module as `config.repo.secrets.global`. - - `backup.pub` Backup age-identity in case I ever lose my YubiKey or it breaks. - - `yk1-nix-rage.pub` Master YubiKey split-identity. Used as a key-grab. -- `pkgs/` Custom packages and scripts -- `users/` User account configuration via home-manager. Imported by each host separately. ## How-To diff --git a/nix/apps/default.nix b/apps/default.nix similarity index 95% rename from nix/apps/default.nix rename to apps/default.nix index 3a8463f..3bd0e6c 100644 --- a/nix/apps/default.nix +++ b/apps/default.nix @@ -12,7 +12,6 @@ }; args = inputs // {inherit pkgs;}; apps = [ - ./draw-graph.nix ./format-secrets.nix ./show-wireguard-qr.nix ]; diff --git a/nix/apps/format-secrets.nix b/apps/format-secrets.nix similarity index 100% rename from nix/apps/format-secrets.nix rename to apps/format-secrets.nix diff --git a/nix/apps/show-wireguard-qr.nix b/apps/show-wireguard-qr.nix similarity index 92% rename from nix/apps/show-wireguard-qr.nix rename to apps/show-wireguard-qr.nix index 1bb67ff..75346bb 100644 --- a/nix/apps/show-wireguard-qr.nix +++ b/apps/show-wireguard-qr.nix @@ -13,13 +13,13 @@ ; nodeNames = attrNames self.nodes; - wireguardNetworks = unique (concatMap (n: attrNames self.nodes.${n}.config.extra.wireguard) nodeNames); + wireguardNetworks = unique (concatMap (n: attrNames self.nodes.${n}.config.meta.wireguard) nodeNames); externalPeersForNet = wgName: concatMap (serverNode: map (peer: {inherit wgName serverNode peer;}) - (attrNames self.nodes.${serverNode}.config.extra.wireguard.${wgName}.server.externalPeers)) + (attrNames self.nodes.${serverNode}.config.meta.wireguard.${wgName}.server.externalPeers)) (self.extraLib.wireguard wgName).participatingServerNodes; allExternalPeers = concatMap externalPeersForNet wireguardNetworks; in diff --git a/flake.nix b/flake.nix index 9cf6c95..7d31f39 100644 --- a/flake.nix +++ b/flake.nix @@ -107,7 +107,7 @@ microvmNodes = nixpkgs.lib.concatMapAttrs (_: node: nixpkgs.lib.mapAttrs' (vm: def: nixpkgs.lib.nameValuePair def.nodeName node.config.microvm.vms.${vm}.config) - (node.config.extra.microvms.vms or {})) + (node.config.meta.microvms.vms or {})) self.colmenaNodes; # Expose all nodes in a single attribute nodes = self.colmenaNodes // self.microvmNodes; @@ -130,7 +130,7 @@ apps = agenix-rekey.defineApps self pkgs self.nodes - // import ./nix/apps inputs system; + // import ./apps inputs system; checks = import ./nix/checks.nix inputs system; devShells.default = import ./nix/dev-shell.nix inputs system; formatter = pkgs.alejandra; diff --git a/hosts/common/bios-boot.nix b/hosts/common/bios-boot.nix deleted file mode 100644 index e578418..0000000 --- a/hosts/common/bios-boot.nix +++ /dev/null @@ -1,10 +0,0 @@ -{lib, ...}: { - boot.loader = { - grub = { - enable = true; - efiSupport = false; - }; - timeout = lib.mkDefault 2; - }; - console.earlySetup = true; -} diff --git a/hosts/common/core/default.nix b/hosts/common/core/default.nix deleted file mode 100644 index 78c1ac4..0000000 --- a/hosts/common/core/default.nix +++ /dev/null @@ -1,44 +0,0 @@ -{config, ...}: { - imports = [ - ./impermanence.nix - ./inputrc.nix - ./issue.nix - ./net.nix - ./nix.nix - ./resolved.nix - ./ssh.nix - ./system.nix - ./xdg.nix - - ../../../users/root - - ../../../modules/deteministic-ids.nix - ../../../modules/distributed-config.nix - ../../../modules/extra.nix - ../../../modules/interface-naming.nix - ../../../modules/microvms.nix - ../../../modules/oauth2-proxy.nix - ../../../modules/promtail.nix - ../../../modules/provided-domains.nix - ../../../modules/repo.nix - ../../../modules/telegraf.nix - ../../../modules/wireguard.nix - ]; - - home-manager = { - useGlobalPkgs = true; - useUserPackages = true; - verbose = true; - }; - - # If the host defines microvms, ensure that this core module and - # some boilerplate is imported automatically. - extra.microvms.commonImports = [ - ./. - {home-manager.users.root.home.minimal = true;} - ]; - - # Required even when using home-manager's zsh module since the /etc/profile load order - # is partly controlled by this. See nix-community/home-manager#3681. - programs.zsh.enable = true; -} diff --git a/hosts/common/core/net.nix b/hosts/common/core/net.nix deleted file mode 100644 index ed093a1..0000000 --- a/hosts/common/core/net.nix +++ /dev/null @@ -1,92 +0,0 @@ -{ - config, - lib, - pkgs, - nodeName, - ... -}: let - inherit - (lib) - concatStringsSep - head - mapAttrsToList - mkDefault - mkForce - ; -in { - networking = { - hostName = nodeName; - useDHCP = mkForce false; - useNetworkd = true; - dhcpcd.enable = false; - - nftables = { - firewall.enable = true; - stopRuleset = mkDefault '' - table inet filter { - chain input { - type filter hook input priority filter; policy drop; - ct state invalid drop - ct state {established, related} accept - - iifname lo accept - meta l4proto ipv6-icmp accept - meta l4proto icmp accept - tcp dport ${toString (head config.services.openssh.ports)} accept - } - chain forward { - type filter hook forward priority filter; policy drop; - } - chain output { - type filter hook output priority filter; policy accept; - } - } - ''; - }; - - # TODO mkForce nftables - nftables.firewall = { - zones = lib.mkForce { - local.localZone = true; - }; - - rules = lib.mkForce { - icmp = { - early = true; - after = ["ct"]; - from = "all"; - to = ["local"]; - extraLines = [ - "ip6 nexthdr icmpv6 icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept" - "ip protocol icmp icmp type { echo-request, destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept" - #"ip6 saddr fe80::/10 ip6 daddr fe80::/10 udp dport 546 accept" # (dhcpv6) - ]; - }; - - ssh = { - early = true; - after = ["ct"]; - from = "all"; - to = ["local"]; - allowedTCPPorts = config.services.openssh.ports; - }; - - untrusted-to-local = { - from = ["untrusted"]; - to = ["local"]; - - inherit - (config.networking.firewall) - allowedTCPPorts - allowedUDPPorts - ; - }; - }; - }; - }; - - systemd.network.enable = true; - - # Rename known network interfaces - extra.networking.renameInterfacesByMac = lib.mapAttrs (_: v: v.mac) (config.repo.secrets.local.networking.interfaces or {}); -} diff --git a/hosts/common/efi.nix b/hosts/common/efi.nix deleted file mode 100644 index 9357f9f..0000000 --- a/hosts/common/efi.nix +++ /dev/null @@ -1,11 +0,0 @@ -{lib, ...}: { - boot.loader = { - efi.canTouchEfiVariables = true; - systemd-boot = { - enable = true; - configurationLimit = 15; - }; - timeout = lib.mkDefault 2; - }; - console.earlySetup = true; -} diff --git a/hosts/nom/default.nix b/hosts/nom/default.nix index 8358712..1b5aade 100644 --- a/hosts/nom/default.nix +++ b/hosts/nom/default.nix @@ -8,19 +8,17 @@ nixos-hardware.common-gpu-intel nixos-hardware.common-pc-laptop nixos-hardware.common-pc-laptop-ssd + ../../modules/optional/hardware/intel.nix + ../../modules/optional/hardware/physical.nix - ../common/core - ../common/dev - ../common/graphical - - ../common/hardware/intel.nix - ../common/hardware/physical.nix - ../common/efi.nix - ../common/initrd-ssh.nix - ../common/laptop.nix - # ../common/sound.nix - ../common/yubikey.nix - ../common/zfs.nix + ../../modules + ../../modules/optional/boot-efi.nix + ../../modules/optional/initrd-ssh.nix + ../../modules/optional/dev + ../../modules/optional/graphical + ../../modules/optional/laptop.nix + #../../modules/optional/sound.nix + ../../modules/optional/zfs.nix ../../users/myuser @@ -30,10 +28,8 @@ boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod"]; - hardware.opengl.enable = true; - console = { font = "ter-v28n"; - packages = with pkgs; [terminus_font]; + packages = [pkgs.terminus_font]; }; } diff --git a/hosts/sentinel/acme.nix b/hosts/sentinel/acme.nix index 180c64c..c710a70 100644 --- a/hosts/sentinel/acme.nix +++ b/hosts/sentinel/acme.nix @@ -17,5 +17,5 @@ in { reloadServices = ["nginx"]; }; }; - extra.acme.wildcardDomains = acme.domains; + security.acme.wildcardDomains = acme.domains; } diff --git a/hosts/sentinel/default.nix b/hosts/sentinel/default.nix index d44550b..d7c107f 100644 --- a/hosts/sentinel/default.nix +++ b/hosts/sentinel/default.nix @@ -4,32 +4,32 @@ ... }: { imports = [ - ../common/core - ../common/hardware/hetzner-cloud.nix - ../common/bios-boot.nix - ../common/initrd-ssh.nix - ../common/zfs.nix + ../../modules/optional/hardware/hetzner-cloud.nix - ./fs.nix - ./net.nix + ../../modules + ../../modules/optional/boot-bios.nix + ../../modules/optional/initrd-ssh.nix + ../../modules/optional/zfs.nix ./acme.nix + ./fs.nix + ./net.nix ./oauth2.nix ]; users.groups.acme.members = ["nginx"]; services.nginx.enable = true; - extra.promtail = { + meta.promtail = { enable = true; proxy = "sentinel"; }; # Connect safely via wireguard to skip authentication - networking.hosts.${config.extra.wireguard.proxy-sentinel.ipv4} = [config.providedDomains.influxdb]; - extra.telegraf = { + networking.hosts.${config.meta.wireguard.proxy-sentinel.ipv4} = [config.networking.providedDomains.influxdb]; + meta.telegraf = { enable = true; - influxdb2.domain = config.providedDomains.influxdb; + influxdb2.domain = config.networking.providedDomains.influxdb; influxdb2.organization = "servers"; influxdb2.bucket = "telegraf"; }; diff --git a/hosts/sentinel/net.nix b/hosts/sentinel/net.nix index 03a2616..e9f70f7 100644 --- a/hosts/sentinel/net.nix +++ b/hosts/sentinel/net.nix @@ -52,7 +52,7 @@ }; }; - extra.wireguard.proxy-sentinel.server = { + meta.wireguard.proxy-sentinel.server = { host = config.networking.fqdn; port = 51443; reservedAddresses = ["10.43.0.0/24" "fd00:43::/120"]; diff --git a/hosts/sentinel/oauth2.nix b/hosts/sentinel/oauth2.nix index f4f4229..6130b11 100644 --- a/hosts/sentinel/oauth2.nix +++ b/hosts/sentinel/oauth2.nix @@ -4,7 +4,7 @@ pkgs, ... }: { - extra.oauth2_proxy = { + meta.oauth2_proxy = { enable = true; cookieDomain = config.repo.secrets.local.personalDomain; portalDomain = "oauth2.${config.repo.secrets.local.personalDomain}"; @@ -22,15 +22,15 @@ in { provider = "oidc"; scope = "openid email"; - loginURL = "https://${config.providedDomains.kanidm}/ui/oauth2"; - redeemURL = "https://${config.providedDomains.kanidm}/oauth2/token"; - validateURL = "https://${config.providedDomains.kanidm}/oauth2/openid/${clientId}/userinfo"; + loginURL = "https://${config.networking.providedDomains.kanidm}/ui/oauth2"; + redeemURL = "https://${config.networking.providedDomains.kanidm}/oauth2/token"; + validateURL = "https://${config.networking.providedDomains.kanidm}/oauth2/openid/${clientId}/userinfo"; clientID = clientId; keyFile = config.age.secrets.oauth2-proxy-secret.path; email.domains = ["*"]; extraConfig = { - oidc-issuer-url = "https://${config.providedDomains.kanidm}/oauth2/openid/${clientId}"; + oidc-issuer-url = "https://${config.networking.providedDomains.kanidm}/oauth2/openid/${clientId}"; provider-display-name = "Kanidm"; #skip-provider-button = true; }; diff --git a/hosts/ward/default.nix b/hosts/ward/default.nix index 42171bd..3f07e42 100644 --- a/hosts/ward/default.nix +++ b/hosts/ward/default.nix @@ -7,13 +7,13 @@ imports = [ nixos-hardware.common-cpu-intel nixos-hardware.common-pc-ssd + ../../modules/optional/hardware/intel.nix + ../../modules/optional/hardware/physical.nix - ../common/core - ../common/hardware/intel.nix - ../common/hardware/physical.nix - ../common/initrd-ssh.nix - ../common/efi.nix - ../common/zfs.nix + ../../modules + ../../modules/optional/boot-efi.nix + ../../modules/optional/initrd-ssh.nix + ../../modules/optional/zfs.nix ./fs.nix ./net.nix @@ -21,16 +21,16 @@ boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" "sdhci_pci" "r8169"]; - extra.promtail = { + meta.promtail = { enable = true; proxy = "sentinel"; }; # Connect safely via wireguard to skip authentication - networking.hosts.${nodes.sentinel.config.extra.wireguard.proxy-sentinel.ipv4} = [nodes.sentinel.config.providedDomains.influxdb]; - extra.telegraf = { + networking.hosts.${nodes.sentinel.config.meta.wireguard.proxy-sentinel.ipv4} = [nodes.sentinel.config.networking.providedDomains.influxdb]; + meta.telegraf = { enable = true; - influxdb2.domain = nodes.sentinel.config.providedDomains.influxdb; + influxdb2.domain = nodes.sentinel.config.networking.providedDomains.influxdb; influxdb2.organization = "servers"; influxdb2.bucket = "telegraf"; }; @@ -38,7 +38,11 @@ # TODO track my github stats # services.telegraf.extraConfig.inputs.github = {}; - extra.microvms.vms = let + meta.microvms.commonImports = [ + ./microvms/common.nix + ]; + + meta.microvms.vms = let defaults = { system = "x86_64-linux"; autostart = true; diff --git a/hosts/ward/microvms/adguardhome/default.nix b/hosts/ward/microvms/adguardhome/default.nix index bdb6ac0..ad4c6ea 100644 --- a/hosts/ward/microvms/adguardhome/default.nix +++ b/hosts/ward/microvms/adguardhome/default.nix @@ -8,30 +8,10 @@ sentinelCfg = nodes.sentinel.config; adguardhomeDomain = "adguardhome.${sentinelCfg.repo.secrets.local.personalDomain}"; in { - imports = [ - ../../../../modules/proxy-via-sentinel.nix - ]; - - extra.promtail = { - enable = true; - proxy = "sentinel"; - }; - - # Connect safely via wireguard to skip authentication - networking.hosts.${sentinelCfg.extra.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.providedDomains.influxdb]; - extra.telegraf = { - enable = true; - influxdb2.domain = sentinelCfg.providedDomains.influxdb; - influxdb2.organization = "servers"; - influxdb2.bucket = "telegraf"; - }; - - networking.nftables.firewall.rules = lib.mkForce { - sentinel-to-local.allowedTCPPorts = [config.services.adguardhome.settings.bind_port]; - }; + meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.adguardhome.settings.bind_port]; nodes.sentinel = { - providedDomains.adguard = adguardhomeDomain; + networking.providedDomains.adguard = adguardhomeDomain; services.nginx = { upstreams.adguardhome = { @@ -43,7 +23,7 @@ in { }; virtualHosts.${adguardhomeDomain} = { forceSSL = true; - useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert adguardhomeDomain; + useACMEWildcardHost = true; oauth2.enable = true; oauth2.allowedGroups = ["access_adguardhome"]; locations."/" = { @@ -57,7 +37,7 @@ in { services.adguardhome = { enable = true; settings = { - bind_host = config.extra.wireguard.proxy-sentinel.ipv4; + bind_host = config.meta.wireguard.proxy-sentinel.ipv4; bind_port = 3000; #dns = { # edns_client_subnet.enabled = false; diff --git a/hosts/ward/microvms/common.nix b/hosts/ward/microvms/common.nix new file mode 100644 index 0000000..c26df64 --- /dev/null +++ b/hosts/ward/microvms/common.nix @@ -0,0 +1,18 @@ +{nodes, ...}: let + sentinelCfg = nodes.sentinel.config; +in { + meta.wireguard-proxy.sentinel = {}; + meta.promtail = { + enable = true; + proxy = "sentinel"; + }; + + # Connect safely via wireguard to skip authentication + networking.hosts.${sentinelCfg.meta.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.networking.providedDomains.influxdb]; + meta.telegraf = { + enable = true; + influxdb2.domain = sentinelCfg.networking.providedDomains.influxdb; + influxdb2.organization = "servers"; + influxdb2.bucket = "telegraf"; + }; +} diff --git a/hosts/ward/microvms/grafana/default.nix b/hosts/ward/microvms/grafana/default.nix index 03a4c87..75031c6 100644 --- a/hosts/ward/microvms/grafana/default.nix +++ b/hosts/ward/microvms/grafana/default.nix @@ -9,27 +9,7 @@ sentinelCfg = nodes.sentinel.config; grafanaDomain = "grafana.${sentinelCfg.repo.secrets.local.personalDomain}"; in { - imports = [ - ../../../../modules/proxy-via-sentinel.nix - ]; - - extra.promtail = { - enable = true; - proxy = "sentinel"; - }; - - # Connect safely via wireguard to skip authentication - networking.hosts.${sentinelCfg.extra.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.providedDomains.influxdb]; - extra.telegraf = { - enable = true; - influxdb2.domain = sentinelCfg.providedDomains.influxdb; - influxdb2.organization = "servers"; - influxdb2.bucket = "telegraf"; - }; - - networking.nftables.firewall.rules = lib.mkForce { - sentinel-to-local.allowedTCPPorts = [config.services.grafana.settings.server.http_port]; - }; + meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.grafana.settings.server.http_port]; age.secrets.grafana-secret-key = { rekeyFile = ./secrets/grafana-secret-key.age; @@ -55,7 +35,7 @@ in { config.age.secrets.grafana-loki-basic-auth-password ]; - providedDomains.grafana = grafanaDomain; + networking.providedDomains.grafana = grafanaDomain; services.nginx = { upstreams.grafana = { @@ -67,7 +47,7 @@ in { }; virtualHosts.${grafanaDomain} = { forceSSL = true; - useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert grafanaDomain; + useACMEWildcardHost = true; locations."/" = { proxyPass = "http://grafana"; proxyWebsockets = true; @@ -87,7 +67,7 @@ in { root_url = "https://${grafanaDomain}"; enforce_domain = true; enable_gzip = true; - http_addr = config.extra.wireguard.proxy-sentinel.ipv4; + http_addr = config.meta.wireguard.proxy-sentinel.ipv4; http_port = 3001; }; @@ -111,9 +91,9 @@ in { client_secret = "aZKNCM6KpjBy4RqwKJXMLXzyx9rKH6MZTFk4wYrKWuBqLj6t"; # TODO temporary test not a real secret scopes = "openid email profile"; login_attribute_path = "prefered_username"; - auth_url = "https://${sentinelCfg.providedDomains.kanidm}/ui/oauth2"; - token_url = "https://${sentinelCfg.providedDomains.kanidm}/oauth2/token"; - api_url = "https://${sentinelCfg.providedDomains.kanidm}/oauth2/openid/grafana/userinfo"; + auth_url = "https://${sentinelCfg.networking.providedDomains.kanidm}/ui/oauth2"; + token_url = "https://${sentinelCfg.networking.providedDomains.kanidm}/oauth2/token"; + api_url = "https://${sentinelCfg.networking.providedDomains.kanidm}/oauth2/openid/grafana/userinfo"; use_pkce = true; # Allow mapping oauth2 roles to server admin allow_assign_grafana_admin = true; @@ -128,7 +108,7 @@ in { name = "InfluxDB (servers)"; type = "influxdb"; access = "proxy"; - url = "https://${sentinelCfg.providedDomains.influxdb}"; + url = "https://${sentinelCfg.networking.providedDomains.influxdb}"; orgId = 1; secureJsonData.token = "$__file{${config.age.secrets.grafana-influxdb-token.path}}"; jsonData.version = "Flux"; @@ -140,7 +120,7 @@ in { name = "Loki"; type = "loki"; access = "proxy"; - url = "https://${sentinelCfg.providedDomains.loki}"; + url = "https://${sentinelCfg.networking.providedDomains.loki}"; orgId = 1; basicAuth = true; basicAuthUser = "${nodeName}+grafana-loki-basic-auth-password"; diff --git a/hosts/ward/microvms/influxdb/default.nix b/hosts/ward/microvms/influxdb/default.nix index 6c804c1..0959f28 100644 --- a/hosts/ward/microvms/influxdb/default.nix +++ b/hosts/ward/microvms/influxdb/default.nix @@ -10,31 +10,10 @@ influxdbPort = 8086; in { microvm.mem = 1024; - - imports = [ - ../../../../modules/proxy-via-sentinel.nix - ]; - - extra.promtail = { - enable = true; - proxy = "sentinel"; - }; - - # Connect safely via wireguard to skip authentication - networking.hosts.${sentinelCfg.extra.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.providedDomains.influxdb]; - extra.telegraf = { - enable = true; - influxdb2.domain = sentinelCfg.providedDomains.influxdb; - influxdb2.organization = "servers"; - influxdb2.bucket = "telegraf"; - }; - - networking.nftables.firewall.rules = lib.mkForce { - sentinel-to-local.allowedTCPPorts = [influxdbPort]; - }; + meta.wireguard-proxy.sentinel.allowedTCPPorts = [influxdbPort]; nodes.sentinel = { - providedDomains.influxdb = influxdbDomain; + networking.providedDomains.influxdb = influxdbDomain; services.nginx = { upstreams.influxdb = { @@ -46,7 +25,7 @@ in { }; virtualHosts.${influxdbDomain} = { forceSSL = true; - useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert influxdbDomain; + useACMEWildcardHost = true; oauth2.enable = true; oauth2.allowedGroups = ["access_influxdb"]; locations."/" = { @@ -54,7 +33,7 @@ in { proxyWebsockets = true; extraConfig = '' satisfy any; - ${lib.concatMapStrings (ip: "allow ${ip};\n") sentinelCfg.extra.wireguard.proxy-sentinel.server.reservedAddresses} + ${lib.concatMapStrings (ip: "allow ${ip};\n") sentinelCfg.meta.wireguard.proxy-sentinel.server.reservedAddresses} deny all; ''; }; @@ -66,7 +45,7 @@ in { enable = true; settings = { reporting-disabled = true; - http-bind-address = "${config.extra.wireguard.proxy-sentinel.ipv4}:${toString influxdbPort}"; + http-bind-address = "${config.meta.wireguard.proxy-sentinel.ipv4}:${toString influxdbPort}"; }; }; diff --git a/hosts/ward/microvms/kanidm/default.nix b/hosts/ward/microvms/kanidm/default.nix index 089a9af..2b7d57c 100644 --- a/hosts/ward/microvms/kanidm/default.nix +++ b/hosts/ward/microvms/kanidm/default.nix @@ -10,27 +10,7 @@ kanidmDomain = "auth.${sentinelCfg.repo.secrets.local.personalDomain}"; kanidmPort = 8300; in { - imports = [ - ../../../../modules/proxy-via-sentinel.nix - ]; - - extra.promtail = { - enable = true; - proxy = "sentinel"; - }; - - # Connect safely via wireguard to skip authentication - networking.hosts.${sentinelCfg.extra.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.providedDomains.influxdb]; - extra.telegraf = { - enable = true; - influxdb2.domain = sentinelCfg.providedDomains.influxdb; - influxdb2.organization = "servers"; - influxdb2.bucket = "telegraf"; - }; - - networking.nftables.firewall.rules = lib.mkForce { - sentinel-to-local.allowedTCPPorts = [kanidmPort]; - }; + meta.wireguard-proxy.sentinel.allowedTCPPorts = [kanidmPort]; age.secrets."kanidm-self-signed.crt" = { rekeyFile = ./secrets/kanidm-self-signed.crt.age; @@ -45,7 +25,7 @@ in { }; nodes.sentinel = { - providedDomains.kanidm = kanidmDomain; + networking.providedDomains.kanidm = kanidmDomain; services.nginx = { upstreams.kanidm = { @@ -57,7 +37,7 @@ in { }; virtualHosts.${kanidmDomain} = { forceSSL = true; - useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert kanidmDomain; + useACMEWildcardHost = true; locations."/".proxyPass = "https://kanidm"; # Allow using self-signed certs to satisfy kanidm's requirement # for TLS connections. (Although this is over wireguard anyway) @@ -76,7 +56,7 @@ in { origin = "https://${kanidmDomain}"; tls_chain = config.age.secrets."kanidm-self-signed.crt".path; tls_key = config.age.secrets."kanidm-self-signed.key".path; - bindaddress = "${config.extra.wireguard.proxy-sentinel.ipv4}:${toString kanidmPort}"; + bindaddress = "${config.meta.wireguard.proxy-sentinel.ipv4}:${toString kanidmPort}"; trust_x_forward_for = true; }; }; diff --git a/hosts/ward/microvms/loki/default.nix b/hosts/ward/microvms/loki/default.nix index 362cded..3b866ae 100644 --- a/hosts/ward/microvms/loki/default.nix +++ b/hosts/ward/microvms/loki/default.nix @@ -8,30 +8,10 @@ sentinelCfg = nodes.sentinel.config; lokiDomain = "loki.${sentinelCfg.repo.secrets.local.personalDomain}"; in { - imports = [ - ../../../../modules/proxy-via-sentinel.nix - ]; - - extra.promtail = { - enable = true; - proxy = "sentinel"; - }; - - # Connect safely via wireguard to skip authentication - networking.hosts.${sentinelCfg.extra.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.providedDomains.influxdb]; - extra.telegraf = { - enable = true; - influxdb2.domain = sentinelCfg.providedDomains.influxdb; - influxdb2.organization = "servers"; - influxdb2.bucket = "telegraf"; - }; - - networking.nftables.firewall.rules = lib.mkForce { - sentinel-to-local.allowedTCPPorts = [config.services.loki.configuration.server.http_listen_port]; - }; + meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.loki.configuration.server.http_listen_port]; nodes.sentinel = { - providedDomains.loki = lokiDomain; + networking.providedDomains.loki = lokiDomain; age.secrets.loki-basic-auth-hashes = { rekeyFile = ./secrets/loki-basic-auth-hashes.age; @@ -52,7 +32,7 @@ in { }; virtualHosts.${lokiDomain} = { forceSSL = true; - useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert lokiDomain; + useACMEWildcardHost = true; locations."/" = { proxyPass = "http://loki"; proxyWebsockets = true; @@ -86,7 +66,7 @@ in { auth_enabled = false; server = { - http_listen_address = config.extra.wireguard.proxy-sentinel.ipv4; + http_listen_address = config.meta.wireguard.proxy-sentinel.ipv4; http_listen_port = 3100; log_level = "warn"; }; diff --git a/hosts/ward/microvms/vaultwarden/default.nix b/hosts/ward/microvms/vaultwarden/default.nix index 9c5c682..62e8ec6 100644 --- a/hosts/ward/microvms/vaultwarden/default.nix +++ b/hosts/ward/microvms/vaultwarden/default.nix @@ -8,68 +8,50 @@ sentinelCfg = nodes.sentinel.config; vaultwardenDomain = "pw.${sentinelCfg.repo.secrets.local.personalDomain}"; in { - imports = [ - ../../../../modules/proxy-via-sentinel.nix + meta.wireguard-proxy.sentinel.allowedTCPPorts = [ + config.services.vaultwarden.config.rocketPort + config.services.vaultwarden.config.websocketPort ]; - extra.promtail = { - enable = true; - proxy = "sentinel"; - }; - - # Connect safely via wireguard to skip authentication - networking.hosts.${sentinelCfg.extra.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.providedDomains.influxdb]; - extra.telegraf = { - enable = true; - influxdb2.domain = sentinelCfg.providedDomains.influxdb; - influxdb2.organization = "servers"; - influxdb2.bucket = "telegraf"; - }; - age.secrets.vaultwarden-env = { rekeyFile = ./secrets/vaultwarden-env.age; mode = "440"; group = "vaultwarden"; }; - networking.nftables.firewall.rules = lib.mkForce { - sentinel-to-local.allowedTCPPorts = [ - config.services.vaultwarden.config.rocketPort - config.services.vaultwarden.config.websocketPort - ]; - }; - nodes.sentinel = { - providedDomains.vaultwarden = vaultwardenDomain; + networking.providedDomains.vaultwarden = vaultwardenDomain; - upstreams.vaultwarden = { - servers."${config.services.vaultwarden.config.rocketAddress}:${toString config.services.vaultwarden.config.rocketPort}" = {}; - extraConfig = '' - zone vaultwarden 64k; - keepalive 2; - ''; - }; - upstreams.vaultwarden-websocket = { - servers."${config.services.vaultwarden.config.websocketAddress}:${toString config.services.vaultwarden.config.websocketPort}" = {}; - extraConfig = '' - zone vaultwarden-websocket 64k; - keepalive 2; - ''; - }; - virtualHosts.${vaultwardenDomain} = { - forceSSL = true; - useACMEHost = sentinelCfg.lib.extra.matchingWildcardCert vaultwardenDomain; - extraConfig = '' - client_max_body_size 256M; - ''; - locations."/".proxyPass = "http://vaultwarden"; - locations."/notifications/hub" = { - proxyPass = "http://vaultwarden-websocket"; - proxyWebsockets = true; + services.nginx = { + upstreams.vaultwarden = { + servers."${config.services.vaultwarden.config.rocketAddress}:${toString config.services.vaultwarden.config.rocketPort}" = {}; + extraConfig = '' + zone vaultwarden 64k; + keepalive 2; + ''; }; - locations."/notifications/hub/negotiate" = { - proxyPass = "http://vaultwarden"; - proxyWebsockets = true; + upstreams.vaultwarden-websocket = { + servers."${config.services.vaultwarden.config.websocketAddress}:${toString config.services.vaultwarden.config.websocketPort}" = {}; + extraConfig = '' + zone vaultwarden-websocket 64k; + keepalive 2; + ''; + }; + virtualHosts.${vaultwardenDomain} = { + forceSSL = true; + useACMEWildcardHost = true; + extraConfig = '' + client_max_body_size 256M; + ''; + locations."/".proxyPass = "http://vaultwarden"; + locations."/notifications/hub" = { + proxyPass = "http://vaultwarden-websocket"; + proxyWebsockets = true; + }; + locations."/notifications/hub/negotiate" = { + proxyPass = "http://vaultwarden"; + proxyWebsockets = true; + }; }; }; }; @@ -84,9 +66,9 @@ in { webVaultEnabled = true; websocketEnabled = true; - websocketAddress = config.extra.wireguard.proxy-sentinel.ipv4; + websocketAddress = config.meta.wireguard.proxy-sentinel.ipv4; websocketPort = 3012; - rocketAddress = config.extra.wireguard.proxy-sentinel.ipv4; + rocketAddress = config.meta.wireguard.proxy-sentinel.ipv4; rocketPort = 8012; signupsAllowed = false; diff --git a/hosts/ward/net.nix b/hosts/ward/net.nix index 41c36e1..2f82b60 100644 --- a/hosts/ward/net.nix +++ b/hosts/ward/net.nix @@ -172,12 +172,12 @@ in { systemd.services.kea-dhcp4-server.after = ["sys-subsystem-net-devices-${utils.escapeSystemdPath "lan-self"}.device"]; - extra.microvms.networking = { + meta.microvms.networking = { baseMac = config.repo.secrets.local.networking.interfaces.lan.mac; macvtapInterface = "lan"; wireguard.openFirewallRules = ["lan-to-local"]; }; # Allow accessing influx - extra.wireguard.proxy-sentinel.client.via = "sentinel"; + meta.wireguard.proxy-sentinel.client.via = "sentinel"; } diff --git a/hosts/zackbiene/default.nix b/hosts/zackbiene/default.nix index f987e31..78763eb 100644 --- a/hosts/zackbiene/default.nix +++ b/hosts/zackbiene/default.nix @@ -6,26 +6,25 @@ ... }: { imports = [ - ../common/core - ../common/hardware/odroid-n2plus.nix - ../common/initrd-ssh.nix - ../common/zfs.nix - ../common/bios-boot.nix + ../../modules/optional/hardware/odroid-n2plus.nix - ./fs.nix - ./net.nix + ../../modules + ../../modules/optional/boot-bios.nix + ../../modules/optional/initrd-ssh.nix + ../../modules/optional/zfs.nix #./dnsmasq.nix ./esphome.nix + ./fs.nix ./home-assistant.nix ./hostapd.nix ./mosquitto.nix + ./net.nix ./nginx.nix ./zigbee2mqtt.nix ]; # TODO boot.loader.grub.devices = ["/dev/disk/by-id/${config.repo.secrets.local.disk.main}"]; - console.earlySetup = true; # Fails if there are no SMART devices services.smartd.enable = lib.mkForce false; diff --git a/hosts/zackbiene/hostapd.nix b/hosts/zackbiene/hostapd.nix index 787e0d8..c475552 100644 --- a/hosts/zackbiene/hostapd.nix +++ b/hosts/zackbiene/hostapd.nix @@ -4,9 +4,6 @@ pkgs, ... }: { - imports = [../../modules/hostapd.nix]; - disabledModules = ["services/networking/hostapd.nix"]; - # Associates each known client to a unique password age.secrets.wifi-clients.rekeyFile = ./secrets/wifi-clients.age; diff --git a/modules/config/boot.nix b/modules/config/boot.nix new file mode 100644 index 0000000..ee0f69a --- /dev/null +++ b/modules/config/boot.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + pkgs, + ... +}: { + boot = { + initrd.systemd = { + enable = true; + emergencyAccess = config.repo.secrets.global.root.hashedPassword; + # TODO good idea? targets.emergency.wants = ["network.target" "sshd.service"]; + extraBin.ip = "${pkgs.iproute2}/bin/ip"; + }; + + # NOTE: Add "rd.systemd.unit=rescue.target" to debug initrd + kernelParams = ["log_buf_len=10M"]; + tmp.useTmpfs = true; + + loader.timeout = lib.mkDefault 2; + }; + + console.earlySetup = true; +} diff --git a/modules/config/home-manager.nix b/modules/config/home-manager.nix new file mode 100644 index 0000000..bce4560 --- /dev/null +++ b/modules/config/home-manager.nix @@ -0,0 +1,12 @@ +{ + home-manager = { + useGlobalPkgs = true; + useUserPackages = true; + verbose = true; + }; + + # Required even when using home-manager's zsh module since the /etc/profile load order + # is partly controlled by this. See nix-community/home-manager#3681. + # TODO remove once we have nushell + programs.zsh.enable = true; +} diff --git a/hosts/common/core/impermanence.nix b/modules/config/impermanence.nix similarity index 100% rename from hosts/common/core/impermanence.nix rename to modules/config/impermanence.nix diff --git a/hosts/common/core/inputrc.nix b/modules/config/inputrc.nix similarity index 100% rename from hosts/common/core/inputrc.nix rename to modules/config/inputrc.nix diff --git a/hosts/common/core/issue.nix b/modules/config/issue.nix similarity index 100% rename from hosts/common/core/issue.nix rename to modules/config/issue.nix diff --git a/hosts/common/core/system.nix b/modules/config/lib.nix similarity index 81% rename from hosts/common/core/system.nix rename to modules/config/lib.nix index 1c66acc..9b56dd1 100644 --- a/hosts/common/core/system.nix +++ b/modules/config/lib.nix @@ -1,10 +1,7 @@ { - config, extraLib, inputs, lib, - nodePath, - pkgs, ... }: { # IP address math library @@ -211,6 +208,7 @@ # Do linear probing. Returns the first unused value at or after the given value. probe = avoid: value: if lib.elem value avoid + # TODO lib.mod # Poor man's modulo, because nix has no modulo. Luckily we operate on a residue # class of x modulo 2^n, so we can use bitAnd instead. then probe avoid (builtins.bitAnd (capacity - 1) (value + 1)) @@ -297,6 +295,7 @@ # Do linear probing. Returns the first unused value at or after the given value. probe = avoid: value: if lib.elem value avoid + # TODO lib.mod # Poor man's modulo, because nix has no modulo. Luckily we operate on a residue # class of x modulo 2^n, so we can use bitAnd instead. then probe avoid (builtins.bitAnd (capacity - 1) (value + 1)) @@ -332,111 +331,4 @@ }; }; }; - - # Define local repo secrets - repo.secretFiles = let - local = nodePath + "/secrets/local.nix.age"; - in - { - global = ../../../secrets/global.nix.age; - } - // lib.optionalAttrs (nodePath != null && lib.pathExists local) {inherit local;}; - - # Setup secret rekeying parameters - age.rekey = { - inherit - (inputs.self.secretsConfig) - masterIdentities - extraEncryptionPubkeys - ; - - # This is technically impure, but intended. We need to rekey on the - # current system due to yubikey availability. - forceRekeyOnSystem = builtins.extraBuiltins.unsafeCurrentSystem; - hostPubkey = let - pubkeyPath = - if nodePath == null - then null - else nodePath + "/secrets/host.pub"; - in - lib.mkIf (pubkeyPath != null && lib.pathExists pubkeyPath) pubkeyPath; - }; - - age.generators.dhparams.script = {pkgs, ...}: "${pkgs.openssl}/bin/openssl dhparam 4096"; - age.generators.basic-auth.script = { - pkgs, - lib, - decrypt, - deps, - ... - }: - lib.flip lib.concatMapStrings deps ({ - name, - host, - file, - }: '' - echo " -> Aggregating "${lib.escapeShellArg host}":"${lib.escapeShellArg name}"" >&2 - ${decrypt} ${lib.escapeShellArg file} \ - | ${pkgs.apacheHttpd}/bin/htpasswd -niBC 12 ${lib.escapeShellArg host}"+"${lib.escapeShellArg name} \ - || die "Failure while aggregating basic auth hashes" - ''); - - boot = { - initrd.systemd = { - enable = true; - emergencyAccess = config.repo.secrets.global.root.hashedPassword; - # TODO good idea? targets.emergency.wants = ["network.target" "sshd.service"]; - extraBin = with pkgs; { - ip = "${iproute2}/bin/ip"; - }; - }; - - # Add "rd.systemd.unit=rescue.target" to debug initrd - kernelParams = ["log_buf_len=10M"]; - tmp.useTmpfs = true; - }; - - # Just before switching, remove the agenix directory if it exists. - # This can happen when a secret is used in the initrd because it will - # then be copied to the initramfs under the same path. This materializes - # /run/agenix as a directory which will cause issues when the actual system tries - # to create a link called /run/agenix. Agenix should probably fail in this case, - # but doesn't and instead puts the generation link into the existing directory. - # TODO See https://github.com/ryantm/agenix/pull/187. - system.activationScripts.removeAgenixLink.text = "[[ ! -L /run/agenix ]] && [[ -d /run/agenix ]] && rm -rf /run/agenix"; - system.activationScripts.agenixNewGeneration.deps = ["removeAgenixLink"]; - - # Disable sudo which is entierly unnecessary. - security.sudo.enable = false; - - time.timeZone = lib.mkDefault "Europe/Berlin"; - i18n.defaultLocale = "C.UTF-8"; - console.keyMap = "de-latin1-nodeadkeys"; - - systemd.enableUnifiedCgroupHierarchy = true; - users.mutableUsers = false; - - users.deterministicIds = let - uidGid = id: { - uid = id; - gid = id; - }; - in { - systemd-oom = uidGid 999; - systemd-coredump = uidGid 998; - sshd = uidGid 997; - nscd = uidGid 996; - polkituser = uidGid 995; - microvm = uidGid 994; - promtail = uidGid 993; - grafana = uidGid 992; - acme = uidGid 991; - kanidm = uidGid 990; - loki = uidGid 989; - vaultwarden = uidGid 988; - oauth2_proxy = uidGid 987; - influxdb2 = uidGid 986; - telegraf = uidGid 985; - rtkit = uidGid 984; - }; } diff --git a/modules/config/microvms.nix b/modules/config/microvms.nix new file mode 100644 index 0000000..df2f957 --- /dev/null +++ b/modules/config/microvms.nix @@ -0,0 +1,8 @@ +{ + # If the host defines microvms, ensure that our modules and + # some boilerplate is imported automatically. + meta.microvms.commonImports = [ + ../. + {home-manager.users.root.home.minimal = true;} + ]; +} diff --git a/modules/config/net.nix b/modules/config/net.nix new file mode 100644 index 0000000..62a9af6 --- /dev/null +++ b/modules/config/net.nix @@ -0,0 +1,20 @@ +{ + config, + lib, + nodeName, + ... +}: { + systemd.network.enable = true; + + networking = { + hostName = nodeName; + useDHCP = lib.mkForce false; + useNetworkd = true; + dhcpcd.enable = false; + + # Rename known network interfaces from local secrets + renameInterfacesByMac = + lib.mapAttrs (_: v: v.mac) + (config.repo.secrets.local.networking.interfaces or {}); + }; +} diff --git a/modules/config/nftables.nix b/modules/config/nftables.nix new file mode 100644 index 0000000..a812b79 --- /dev/null +++ b/modules/config/nftables.nix @@ -0,0 +1,70 @@ +{ + config, + lib, + ... +}: { + networking.nftables = { + stopRuleset = lib.mkDefault '' + table inet filter { + chain input { + type filter hook input priority filter; policy drop; + ct state invalid drop + ct state {established, related} accept + + iifname lo accept + meta l4proto ipv6-icmp accept + meta l4proto icmp accept + tcp dport ${toString (lib.head config.services.openssh.ports)} accept + } + chain forward { + type filter hook forward priority filter; policy drop; + } + chain output { + type filter hook output priority filter; policy accept; + } + } + ''; + + firewall = { + enable = true; + + # TODO mkForce nftables + zones = lib.mkForce { + local.localZone = true; + }; + + rules = lib.mkForce { + icmp = { + early = true; + after = ["ct"]; + from = "all"; + to = ["local"]; + extraLines = [ + "ip6 nexthdr icmpv6 icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept" + "ip protocol icmp icmp type { echo-request, destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept" + #"ip6 saddr fe80::/10 ip6 daddr fe80::/10 udp dport 546 accept" # (dhcpv6) + ]; + }; + + ssh = { + early = true; + after = ["ct"]; + from = "all"; + to = ["local"]; + allowedTCPPorts = config.services.openssh.ports; + }; + + untrusted-to-local = { + from = ["untrusted"]; + to = ["local"]; + + inherit + (config.networking.firewall) + allowedTCPPorts + allowedUDPPorts + ; + }; + }; + }; + }; +} diff --git a/hosts/common/core/nix.nix b/modules/config/nix.nix similarity index 100% rename from hosts/common/core/nix.nix rename to modules/config/nix.nix diff --git a/hosts/common/core/resolved.nix b/modules/config/resolved.nix similarity index 96% rename from hosts/common/core/resolved.nix rename to modules/config/resolved.nix index 1410059..9f10365 100644 --- a/hosts/common/core/resolved.nix +++ b/modules/config/resolved.nix @@ -24,7 +24,6 @@ (lib.mkAfter ["mdns"]) ]; - # TODO mkForce nftables # Open port 5353 for any interfaces that have MulticastDNS enabled networking.nftables.firewall = let # Determine all networks that have MulticastDNS enabled @@ -38,7 +37,7 @@ knownMacs = lib.mapAttrs' (k: v: lib.nameValuePair v k) - config.extra.networking.renameInterfacesByMac; + config.networking.renameInterfacesByMac; # A helper that returns the link name for the given mac address, # or null if it doesn't exist or the given mac was null. linkNameFor = mac: @@ -61,6 +60,7 @@ ); in lib.mkIf (mdnsInterfaces != []) { + # TODO mkForce nftables zones = lib.mkForce { mdns.interfaces = mdnsInterfaces; }; diff --git a/modules/config/secrets.nix b/modules/config/secrets.nix new file mode 100644 index 0000000..e7ed12a --- /dev/null +++ b/modules/config/secrets.nix @@ -0,0 +1,64 @@ +{ + inputs, + lib, + nodePath, + ... +}: { + # Define local repo secrets + repo.secretFiles = let + local = nodePath + "/secrets/local.nix.age"; + in + { + global = ../../secrets/global.nix.age; + } + // lib.optionalAttrs (nodePath != null && lib.pathExists local) {inherit local;}; + + # Setup secret rekeying parameters + age.rekey = { + inherit + (inputs.self.secretsConfig) + masterIdentities + extraEncryptionPubkeys + ; + + # This is technically impure, but intended. We need to rekey on the + # current system due to yubikey availability. + forceRekeyOnSystem = builtins.extraBuiltins.unsafeCurrentSystem; + hostPubkey = let + pubkeyPath = + if nodePath == null + then null + else nodePath + "/secrets/host.pub"; + in + lib.mkIf (pubkeyPath != null && lib.pathExists pubkeyPath) pubkeyPath; + }; + + age.generators.dhparams.script = {pkgs, ...}: "${pkgs.openssl}/bin/openssl dhparam 4096"; + age.generators.basic-auth.script = { + pkgs, + lib, + decrypt, + deps, + ... + }: + lib.flip lib.concatMapStrings deps ({ + name, + host, + file, + }: '' + echo " -> Aggregating "${lib.escapeShellArg host}":"${lib.escapeShellArg name}"" >&2 + ${decrypt} ${lib.escapeShellArg file} \ + | ${pkgs.apacheHttpd}/bin/htpasswd -niBC 12 ${lib.escapeShellArg host}"+"${lib.escapeShellArg name} \ + || die "Failure while aggregating basic auth hashes" + ''); + + # Just before switching, remove the agenix directory if it exists. + # This can happen when a secret is used in the initrd because it will + # then be copied to the initramfs under the same path. This materializes + # /run/agenix as a directory which will cause issues when the actual system tries + # to create a link called /run/agenix. Agenix should probably fail in this case, + # but doesn't and instead puts the generation link into the existing directory. + # TODO See https://github.com/ryantm/agenix/pull/187. + system.activationScripts.removeAgenixLink.text = "[[ ! -L /run/agenix ]] && [[ -d /run/agenix ]] && rm -rf /run/agenix"; + system.activationScripts.agenixNewGeneration.deps = ["removeAgenixLink"]; +} diff --git a/hosts/common/core/ssh.nix b/modules/config/ssh.nix similarity index 100% rename from hosts/common/core/ssh.nix rename to modules/config/ssh.nix diff --git a/modules/config/system.nix b/modules/config/system.nix new file mode 100644 index 0000000..5dfb651 --- /dev/null +++ b/modules/config/system.nix @@ -0,0 +1,10 @@ +{lib, ...}: { + # Disable sudo which is entierly unnecessary. + security.sudo.enable = false; + + time.timeZone = lib.mkDefault "Europe/Berlin"; + i18n.defaultLocale = "C.UTF-8"; + console.keyMap = "de-latin1-nodeadkeys"; + + systemd.enableUnifiedCgroupHierarchy = true; +} diff --git a/modules/config/users.nix b/modules/config/users.nix new file mode 100644 index 0000000..6e7ffb4 --- /dev/null +++ b/modules/config/users.nix @@ -0,0 +1,27 @@ +{ + users.mutableUsers = false; + + users.deterministicIds = let + uidGid = id: { + uid = id; + gid = id; + }; + in { + systemd-oom = uidGid 999; + systemd-coredump = uidGid 998; + sshd = uidGid 997; + nscd = uidGid 996; + polkituser = uidGid 995; + microvm = uidGid 994; + promtail = uidGid 993; + grafana = uidGid 992; + acme = uidGid 991; + kanidm = uidGid 990; + loki = uidGid 989; + vaultwarden = uidGid 988; + oauth2_proxy = uidGid 987; + influxdb2 = uidGid 986; + telegraf = uidGid 985; + rtkit = uidGid 984; + }; +} diff --git a/hosts/common/core/xdg.nix b/modules/config/xdg.nix similarity index 100% rename from hosts/common/core/xdg.nix rename to modules/config/xdg.nix diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..4ed4918 --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,42 @@ +{ + imports = [ + ../users/root + + ./config/boot.nix + ./config/home-manager.nix + ./config/impermanence.nix + ./config/inputrc.nix + ./config/issue.nix + ./config/lib.nix + ./config/microvms.nix + ./config/net.nix + ./config/nftables.nix + ./config/nix.nix + ./config/resolved.nix + ./config/secrets.nix + ./config/ssh.nix + ./config/system.nix + ./config/users.nix + ./config/xdg.nix + + ./meta/microvms.nix + ./meta/nginx.nix + ./meta/oauth2-proxy.nix + ./meta/promtail.nix + ./meta/telegraf.nix + ./meta/wireguard-proxy.nix + ./meta/wireguard.nix + + ./networking/hostapd.nix + ./networking/interface-naming.nix + ./networking/provided-domains.nix + + ./repo/distributed-config.nix + ./repo/meta.nix + ./repo/secrets.nix + + ./security/acme-wildcard.nix + + ./system/deteministic-ids.nix + ]; +} diff --git a/modules/extra.nix b/modules/extra.nix deleted file mode 100644 index 49b6a16..0000000 --- a/modules/extra.nix +++ /dev/null @@ -1,136 +0,0 @@ -{ - config, - lib, - nodePath, - ... -}: let - inherit - (lib) - assertMsg - filter - flip - genAttrs - hasInfix - head - mapAttrs - mapAttrs' - mdDoc - mkIf - mkOption - nameValuePair - optionals - removeSuffix - types - ; -in { - options.extra = { - acme.wildcardDomains = mkOption { - default = []; - example = ["example.org"]; - type = types.listOf types.str; - description = mdDoc '' - All domains for which a wildcard certificate will be generated. - This will define the given `security.acme.certs` and set `extraDomainNames` correctly, - but does not fill any options such as credentials or dnsProvider. These have to be set - individually for each cert by the user or via `security.acme.defaults`. - ''; - }; - }; - - options.services.nginx.virtualHosts = mkOption { - type = types.attrsOf (types.submodule ({config, ...}: { - options.recommendedSecurityHeaders = mkOption { - type = types.bool; - default = true; - description = mdDoc ''Whether to add additional security headers to the "/" location.''; - }; - config = mkIf config.recommendedSecurityHeaders { - locations."/".extraConfig = '' - # Enable HTTP Strict Transport Security (HSTS) - add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; - - # Minimize information leaked to other domains - add_header Referrer-Policy "origin-when-cross-origin"; - - add_header X-XSS-Protection "1; mode=block"; - add_header X-Frame-Options "DENY"; - add_header X-Content-Type-Options "nosniff"; - ''; - }; - })); - }; - - config = { - lib.extra = { - # For a given domain, this searches for a matching wildcard acme domain that - # would include the given domain. If no such domain is defined in - # extra.acme.wildcardDomains, an assertion is triggered. - matchingWildcardCert = domain: let - matchingCerts = - filter - (x: !hasInfix "." (removeSuffix ".${x}" domain)) - config.extra.acme.wildcardDomains; - in - assert assertMsg (matchingCerts != []) "No wildcard certificate was defined that matches ${domain}"; - head matchingCerts; - }; - - security.acme.certs = genAttrs config.extra.acme.wildcardDomains (domain: { - extraDomainNames = ["*.${domain}"]; - }); - - age.secrets = mkIf config.services.nginx.enable { - "dhparams.pem" = { - rekeyFile = nodePath + "/secrets/dhparams.pem.age"; - generator = "dhparams"; - mode = "440"; - group = "nginx"; - }; - }; - - # Sensible defaults for nginx - services.nginx = mkIf config.services.nginx.enable { - recommendedBrotliSettings = true; - recommendedGzipSettings = true; - recommendedOptimisation = true; - recommendedProxySettings = true; - recommendedTlsSettings = true; - - # SSL config - sslCiphers = "EECDH+AESGCM:EDH+AESGCM:!aNULL"; - sslDhparam = config.age.secrets."dhparams.pem".path; - commonHttpConfig = '' - log_format json_combined escape=json '{' - '"time": $msec,' - '"remote_addr":"$remote_addr",' - '"status":$status,' - '"method":"$request_method",' - '"host":"$host",' - '"uri":"$request_uri",' - '"request_size":$request_length,' - '"response_size":$body_bytes_sent,' - '"response_time":$request_time,' - '"referrer":"$http_referer",' - '"user_agent":"$http_user_agent"' - '}'; - error_log syslog:server=unix:/dev/log,nohostname; - access_log syslog:server=unix:/dev/log,nohostname json_combined; - ssl_ecdh_curve secp384r1; - ''; - - virtualHosts.localhost = { - locations."= /nginx_status".extraConfig = '' - allow 127.0.0.0/8; - deny all; - stub_status; - ''; - }; - }; - - networking.firewall.allowedTCPPorts = optionals config.services.nginx.enable [80 443]; - - services.telegraf.extraConfig.inputs = mkIf config.services.nginx.enable { - nginx.urls = ["http://localhost/nginx_status"]; - }; - }; -} diff --git a/modules/microvms.nix b/modules/meta/microvms.nix similarity index 96% rename from modules/microvms.nix rename to modules/meta/microvms.nix index 7b73696..ac370df 100644 --- a/modules/microvms.nix +++ b/modules/meta/microvms.nix @@ -35,8 +35,8 @@ ; parentConfig = config; - cfg = config.extra.microvms; - inherit (config.extra.microvms) vms; + cfg = config.meta.microvms; + inherit (config.meta.microvms) vms; inherit (config.lib) net; # Configuration for each microvm @@ -94,7 +94,7 @@ nodes = mkMerge config.microvm.vms.${vmName}.config.options.nodes.definitions; microvm.vms.${vmName} = let - node = import ../nix/generate-node.nix inputs vmCfg.nodeName { + node = import ../../nix/generate-node.nix inputs vmCfg.nodeName { inherit (vmCfg) system configPath; }; mac = (net.mac.assignMacs "02:01:27:00:00:00" 24 [] (attrNames vms)).${vmName}; @@ -165,7 +165,7 @@ gc.automatic = mkForce false; }; - extra.networking.renameInterfacesByMac.${vmCfg.networking.mainLinkName} = mac; + networking.renameInterfacesByMac.${vmCfg.networking.mainLinkName} = mac; systemd.network.networks = { @@ -186,7 +186,7 @@ # would not come online if the private key wasn't rekeyed yet). # FIXME ideally this would be conditional at runtime if the # agenix activation had an error, but this is not trivial. - ${parentConfig.extra.wireguard."${nodeName}-local-vms".unitConfName} = { + ${parentConfig.meta.wireguard."${nodeName}-local-vms".unitConfName} = { linkConfig.RequiredForOnline = "no"; }; }; @@ -198,7 +198,7 @@ }; }; - extra.wireguard = mkIf vmCfg.localWireguard { + meta.wireguard = mkIf vmCfg.localWireguard { "${nodeName}-local-vms" = { server = { host = @@ -222,7 +222,7 @@ in { {microvm.host.enable = vms != {};} ]; - options.extra.microvms = { + options.meta.microvms = { commonImports = mkOption { type = types.listOf types.unspecified; default = []; @@ -362,7 +362,7 @@ in { config = mkIf (vms != {}) ( { # Define a local wireguard server to communicate with vms securely - extra.wireguard = mkIf (any (x: x.localWireguard) (attrValues vms)) { + meta.wireguard = mkIf (any (x: x.localWireguard) (attrValues vms)) { "${nodeName}-local-vms" = { server = { host = diff --git a/modules/meta/nginx.nix b/modules/meta/nginx.nix new file mode 100644 index 0000000..2147828 --- /dev/null +++ b/modules/meta/nginx.nix @@ -0,0 +1,79 @@ +{ + config, + lib, + nodePath, + ... +}: let + inherit + (lib) + mdDoc + mkIf + mkOption + types + ; +in { + options.services.nginx.virtualHosts = mkOption { + type = types.attrsOf (types.submodule ({config, ...}: { + options.recommendedSecurityHeaders = mkOption { + type = types.bool; + default = true; + description = mdDoc ''Whether to add additional security headers to the "/" location.''; + }; + config = mkIf config.recommendedSecurityHeaders { + locations."/".extraConfig = '' + # Enable HTTP Strict Transport Security (HSTS) + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + + # Minimize information leaked to other domains + add_header Referrer-Policy "origin-when-cross-origin"; + + add_header X-XSS-Protection "1; mode=block"; + add_header X-Frame-Options "DENY"; + add_header X-Content-Type-Options "nosniff"; + ''; + }; + })); + }; + + config = mkIf config.services.nginx.enable { + age.secrets."dhparams.pem" = { + rekeyFile = nodePath + "/secrets/dhparams.pem.age"; + generator = "dhparams"; + mode = "440"; + group = "nginx"; + }; + + # Sensible defaults for nginx + services.nginx = { + recommendedBrotliSettings = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + + # SSL config + sslCiphers = "EECDH+AESGCM:EDH+AESGCM:!aNULL"; + sslDhparam = config.age.secrets."dhparams.pem".path; + commonHttpConfig = '' + log_format json_combined escape=json '{' + '"time": $msec,' + '"remote_addr":"$remote_addr",' + '"status":$status,' + '"method":"$request_method",' + '"host":"$host",' + '"uri":"$request_uri",' + '"request_size":$request_length,' + '"response_size":$body_bytes_sent,' + '"response_time":$request_time,' + '"referrer":"$http_referer",' + '"user_agent":"$http_user_agent"' + '}'; + error_log syslog:server=unix:/dev/log,nohostname; + access_log syslog:server=unix:/dev/log,nohostname json_combined; + ssl_ecdh_curve secp384r1; + ''; + }; + + networking.firewall.allowedTCPPorts = [80 443]; + }; +} diff --git a/modules/oauth2-proxy.nix b/modules/meta/oauth2-proxy.nix similarity index 96% rename from modules/oauth2-proxy.nix rename to modules/meta/oauth2-proxy.nix index 35855dc..f0efa55 100644 --- a/modules/oauth2-proxy.nix +++ b/modules/meta/oauth2-proxy.nix @@ -17,9 +17,9 @@ types ; - cfg = config.extra.oauth2_proxy; + cfg = config.meta.oauth2_proxy; in { - options.extra.oauth2_proxy = { + options.meta.oauth2_proxy = { enable = mkEnableOption (mdDoc "oauth2 proxy"); cookieDomain = mkOption { @@ -141,7 +141,7 @@ in { virtualHosts.${cfg.portalDomain} = { forceSSL = true; - useACMEHost = config.lib.extra.matchingWildcardCert cfg.portalDomain; + useACMEWildcardHost = true; oauth2.enable = true; locations."/".proxyPass = "http://oauth2_proxy"; }; diff --git a/modules/promtail.nix b/modules/meta/promtail.nix similarity index 96% rename from modules/promtail.nix rename to modules/meta/promtail.nix index 5b9b86e..993fe58 100644 --- a/modules/promtail.nix +++ b/modules/meta/promtail.nix @@ -15,9 +15,9 @@ types ; - cfg = config.extra.promtail; + cfg = config.meta.promtail; in { - options.extra.promtail = { + options.meta.promtail = { enable = mkEnableOption (mdDoc "promtail to push logs to a loki instance."); proxy = mkOption { type = types.str; @@ -50,7 +50,7 @@ in { { basic_auth.username = "${nodeName}+promtail-loki-basic-auth-password"; basic_auth.password_file = config.age.secrets.promtail-loki-basic-auth-password.path; - url = "https://${nodes.${cfg.proxy}.config.providedDomains.loki}/loki/api/v1/push"; + url = "https://${nodes.${cfg.proxy}.config.networking.providedDomains.loki}/loki/api/v1/push"; } ]; diff --git a/modules/telegraf.nix b/modules/meta/telegraf.nix similarity index 90% rename from modules/telegraf.nix rename to modules/meta/telegraf.nix index daf54e7..53fa550 100644 --- a/modules/telegraf.nix +++ b/modules/meta/telegraf.nix @@ -17,9 +17,9 @@ types ; - cfg = config.extra.telegraf; + cfg = config.meta.telegraf; in { - options.extra.telegraf = { + options.meta.telegraf = { enable = mkEnableOption (mdDoc "telegraf to push metrics to influx."); influxdb2 = { domain = mkOption { @@ -111,12 +111,23 @@ in { path_smartctl = "${pkgs.smartmontools}/bin/smartctl"; use_sudo = true; }; + } + // optionalAttrs config.services.nginx.enable { + nginx.urls = ["http://localhost/nginx_status"]; # TODO } // optionalAttrs config.services.iwd.enable { # TODO wireless = { }; }; }; }; + services.nginx.virtualHosts = mkIf config.services.telegraf.enable { + localhost.locations."= /nginx_status".extraConfig = '' + allow 127.0.0.0/8; + deny all; + stub_status; + ''; + }; + systemd.services.telegraf = { path = [ "/run/wrappers" diff --git a/modules/meta/wireguard-proxy.nix b/modules/meta/wireguard-proxy.nix new file mode 100644 index 0000000..1541cbe --- /dev/null +++ b/modules/meta/wireguard-proxy.nix @@ -0,0 +1,81 @@ +{ + config, + lib, + nodes, + ... +}: let + inherit + (lib) + attrNames + flip + mdDoc + mkForce + mkIf + mkMerge + mkOption + types + ; + + cfg = config.meta.wireguard-proxy; +in { + options.meta.wireguard-proxy = mkOption { + default = {}; + description = mdDoc '' + Each entry here will setup a wireguard network that connects via the + given node and adds appropriate firewall zones. There will a zone for + the interface and one for the proxy server specifically. A corresponding + rule `''${name}-to-local` will be created to easily expose services to the proxy. + ''; + type = types.attrsOf (types.submodule ({name, ...}: { + options = { + nicName = mkOption { + type = types.str; + default = "proxy-${name}"; + description = mdDoc "The name for the created wireguard network and its interface"; + }; + allowedTCPPorts = mkOption { + type = types.listOf types.int; + default = []; + description = mdDoc "Convenience option to allow incoming TCP connections from the proxy server (just the server, not the entire network)."; + }; + allowedUDPPorts = mkOption { + type = types.listOf types.int; + default = []; + description = mdDoc "Convenience option to allow incoming UDP connections from the proxy server (just the server, not the entire network)."; + }; + }; + })); + }; + + config = mkIf (cfg != {}) { + meta.wireguard = mkMerge (flip map (attrNames cfg) (proxy: { + ${cfg.${proxy}.nicName}.client.via = proxy; + })); + + networking.nftables.firewall = mkMerge (flip map (attrNames cfg) (proxy: { + zones = mkForce { + # Parent zone for the whole interface + ${cfg.${proxy}.nicName}.interfaces = [cfg.${proxy}.nicName]; + # Subzone to specifically target the proxy host + ${proxy} = { + parent = cfg.${proxy}.nicName; + ipv4Addresses = [nodes.${proxy}.config.meta.wireguard.${cfg.${proxy}.nicName}.ipv4]; + ipv6Addresses = [nodes.${proxy}.config.meta.wireguard.${cfg.${proxy}.nicName}.ipv6]; + }; + }; + + rules = mkForce { + "${proxy}-to-local" = { + from = [proxy]; + to = ["local"]; + + inherit + (cfg.${proxy}) + allowedTCPPorts + allowedUDPPorts + ; + }; + }; + })); + }; +} diff --git a/modules/wireguard.nix b/modules/meta/wireguard.nix similarity index 99% rename from modules/wireguard.nix rename to modules/meta/wireguard.nix index 1bc31b1..99fced4 100644 --- a/modules/wireguard.nix +++ b/modules/meta/wireguard.nix @@ -44,7 +44,7 @@ ; inherit (config.lib) net; - cfg = config.extra.wireguard; + cfg = config.meta.wireguard; configForNetwork = wgName: wgCfg: let inherit @@ -258,7 +258,7 @@ }; }; in { - options.extra.wireguard = mkOption { + options.meta.wireguard = mkOption { default = {}; description = "Configures wireguard networks via systemd-networkd."; type = types.lazyAttrsOf (types.submodule ({ diff --git a/modules/hostapd.nix b/modules/networking/hostapd.nix similarity index 99% rename from modules/hostapd.nix rename to modules/networking/hostapd.nix index fc5c98c..335b6ca 100644 --- a/modules/hostapd.nix +++ b/modules/networking/hostapd.nix @@ -1193,4 +1193,5 @@ in { }; }; }; + disabledModules = ["services/networking/hostapd.nix"]; } diff --git a/modules/interface-naming.nix b/modules/networking/interface-naming.nix similarity index 86% rename from modules/interface-naming.nix rename to modules/networking/interface-naming.nix index 2a7e67b..c8ab7de 100644 --- a/modules/interface-naming.nix +++ b/modules/networking/interface-naming.nix @@ -1,3 +1,4 @@ +# Provides an option to easily rename interfaces by their mac addresses. { config, extraLib, @@ -15,7 +16,7 @@ types ; - cfg = config.extra.networking.renameInterfacesByMac; + cfg = config.networking.renameInterfacesByMac; interfaceNamesUdevRules = pkgs.writeTextFile { name = "interface-names-udev-rules"; @@ -25,7 +26,7 @@ destination = "/etc/udev/rules.d/01-interface-names.rules"; }; in { - options.extra.networking.renameInterfacesByMac = mkOption { + options.networking.renameInterfacesByMac = mkOption { default = {}; example = {lan = "11:22:33:44:55:66";}; description = "Allows naming of network interfaces based on their physical address"; diff --git a/modules/provided-domains.nix b/modules/networking/provided-domains.nix similarity index 81% rename from modules/provided-domains.nix rename to modules/networking/provided-domains.nix index 62f86fb..b09fa3a 100644 --- a/modules/provided-domains.nix +++ b/modules/networking/provided-domains.nix @@ -1,5 +1,5 @@ {lib, ...}: { - options.providedDomains = lib.mkOption { + options.networking.providedDomains = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = {}; description = "Registry of domains that this host 'provides' (that refer to this host with some functionality). For easy cross-node referencing."; diff --git a/modules/optional/boot-bios.nix b/modules/optional/boot-bios.nix new file mode 100644 index 0000000..c8d47a6 --- /dev/null +++ b/modules/optional/boot-bios.nix @@ -0,0 +1,7 @@ +{ + boot.loader.grub = { + enable = true; + efiSupport = false; + configurationLimit = 32; + }; +} diff --git a/modules/optional/boot-efi.nix b/modules/optional/boot-efi.nix new file mode 100644 index 0000000..6765bee --- /dev/null +++ b/modules/optional/boot-efi.nix @@ -0,0 +1,7 @@ +{ + boot.loader = { + systemd-boot.enable = true; + systemd-boot.configurationLimit = 32; + efi.canTouchEfiVariables = true; + }; +} diff --git a/hosts/common/dev/default.nix b/modules/optional/dev/default.nix similarity index 87% rename from hosts/common/dev/default.nix rename to modules/optional/dev/default.nix index 0b5f0d7..ee9af68 100644 --- a/hosts/common/dev/default.nix +++ b/modules/optional/dev/default.nix @@ -1,6 +1,7 @@ { imports = [ ./documentation.nix + ./yubikey.nix ]; environment.enableDebugInfo = true; diff --git a/hosts/common/dev/documentation.nix b/modules/optional/dev/documentation.nix similarity index 100% rename from hosts/common/dev/documentation.nix rename to modules/optional/dev/documentation.nix diff --git a/hosts/common/yubikey.nix b/modules/optional/dev/yubikey.nix similarity index 100% rename from hosts/common/yubikey.nix rename to modules/optional/dev/yubikey.nix diff --git a/hosts/common/graphical/default.nix b/modules/optional/graphical/default.nix similarity index 100% rename from hosts/common/graphical/default.nix rename to modules/optional/graphical/default.nix diff --git a/hosts/common/graphical/fonts.nix b/modules/optional/graphical/fonts.nix similarity index 100% rename from hosts/common/graphical/fonts.nix rename to modules/optional/graphical/fonts.nix diff --git a/hosts/common/graphical/wayland.nix b/modules/optional/graphical/wayland.nix similarity index 100% rename from hosts/common/graphical/wayland.nix rename to modules/optional/graphical/wayland.nix diff --git a/hosts/common/hardware/bluetooth.nix b/modules/optional/hardware/bluetooth.nix similarity index 100% rename from hosts/common/hardware/bluetooth.nix rename to modules/optional/hardware/bluetooth.nix diff --git a/hosts/common/hardware/hetzner-cloud.nix b/modules/optional/hardware/hetzner-cloud.nix similarity index 100% rename from hosts/common/hardware/hetzner-cloud.nix rename to modules/optional/hardware/hetzner-cloud.nix diff --git a/hosts/common/hardware/intel.nix b/modules/optional/hardware/intel.nix similarity index 100% rename from hosts/common/hardware/intel.nix rename to modules/optional/hardware/intel.nix diff --git a/hosts/common/hardware/nvidia.nix b/modules/optional/hardware/nvidia.nix similarity index 75% rename from hosts/common/hardware/nvidia.nix rename to modules/optional/hardware/nvidia.nix index 384ba86..7d97703 100644 --- a/hosts/common/hardware/nvidia.nix +++ b/modules/optional/hardware/nvidia.nix @@ -13,7 +13,4 @@ }; services.xserver.videoDrivers = ["nvidia"]; - - virtualisation.docker.enableNvidia = true; - virtualisation.podman.enableNvidia = true; } diff --git a/hosts/common/hardware/odroid-n2plus.nix b/modules/optional/hardware/odroid-n2plus.nix similarity index 100% rename from hosts/common/hardware/odroid-n2plus.nix rename to modules/optional/hardware/odroid-n2plus.nix diff --git a/hosts/common/hardware/physical.nix b/modules/optional/hardware/physical.nix similarity index 100% rename from hosts/common/hardware/physical.nix rename to modules/optional/hardware/physical.nix diff --git a/hosts/common/initrd-ssh.nix b/modules/optional/initrd-ssh.nix similarity index 100% rename from hosts/common/initrd-ssh.nix rename to modules/optional/initrd-ssh.nix diff --git a/hosts/common/laptop.nix b/modules/optional/laptop.nix similarity index 100% rename from hosts/common/laptop.nix rename to modules/optional/laptop.nix diff --git a/hosts/common/sound.nix b/modules/optional/sound.nix similarity index 100% rename from hosts/common/sound.nix rename to modules/optional/sound.nix diff --git a/hosts/common/zfs.nix b/modules/optional/zfs.nix similarity index 100% rename from hosts/common/zfs.nix rename to modules/optional/zfs.nix diff --git a/modules/proxy-via-sentinel.nix b/modules/proxy-via-sentinel.nix deleted file mode 100644 index a54712d..0000000 --- a/modules/proxy-via-sentinel.nix +++ /dev/null @@ -1,25 +0,0 @@ -{ - lib, - nodes, - ... -}: { - extra.wireguard.proxy-sentinel.client.via = "sentinel"; - - networking.nftables.firewall = { - zones = lib.mkForce { - proxy-sentinel.interfaces = ["proxy-sentinel"]; - sentinel = { - parent = "proxy-sentinel"; - ipv4Addresses = [nodes.sentinel.config.extra.wireguard.proxy-sentinel.ipv4]; - ipv6Addresses = [nodes.sentinel.config.extra.wireguard.proxy-sentinel.ipv6]; - }; - }; - - rules = lib.mkForce { - sentinel-to-local = { - from = ["sentinel"]; - to = ["local"]; - }; - }; - }; -} diff --git a/modules/distributed-config.nix b/modules/repo/distributed-config.nix similarity index 91% rename from modules/distributed-config.nix rename to modules/repo/distributed-config.nix index f39a27e..c403f90 100644 --- a/modules/distributed-config.nix +++ b/modules/repo/distributed-config.nix @@ -11,7 +11,6 @@ attrNames concatMap elem - filter mdDoc mkOption mkOptionType @@ -37,7 +36,7 @@ in { allNodes = attrNames colmenaNodes; isColmenaNode = elem nodeName allNodes; foreignConfigs = concatMap (n: colmenaNodes.${n}.config.nodes.${nodeName} or []) allNodes; - toplevelAttrs = ["age" "providedDomains" "networking" "systemd" "services"]; + toplevelAttrs = ["age" "networking" "systemd" "services"]; in optionalAttrs isColmenaNode (mergeToplevelConfigs toplevelAttrs ( foreignConfigs diff --git a/modules/repo/meta.nix b/modules/repo/meta.nix new file mode 100644 index 0000000..0c27c46 --- /dev/null +++ b/modules/repo/meta.nix @@ -0,0 +1,19 @@ +{} +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way +# TODO define special args in a more documented and readOnly accessible way + diff --git a/modules/repo.nix b/modules/repo/secrets.nix similarity index 97% rename from modules/repo.nix rename to modules/repo/secrets.nix index b844eb0..811f883 100644 --- a/modules/repo.nix +++ b/modules/repo/secrets.nix @@ -88,7 +88,7 @@ in { # at least via its parent folder so it can access relative files. nix.extraOptions = mkIf cfg.defineNixExtraBuiltins '' plugin-files = ${pkgs.nix-plugins}/lib/nix/plugins - extra-builtins-file = ${../nix}/extra-builtins.nix + extra-builtins-file = ${inputs.self.outPath}/nix/extra-builtins.nix ''; }; } diff --git a/modules/security/acme-wildcard.nix b/modules/security/acme-wildcard.nix new file mode 100644 index 0000000..b0f73c9 --- /dev/null +++ b/modules/security/acme-wildcard.nix @@ -0,0 +1,59 @@ +{ + config, + lib, + ... +}: let + inherit + (lib) + assertMsg + filter + genAttrs + hasInfix + head + mdDoc + mkIf + mkOption + removeSuffix + types + ; +in { + options.security.acme.wildcardDomains = mkOption { + default = []; + example = ["example.org"]; + type = types.listOf types.str; + description = mdDoc '' + All domains for which a wildcard certificate will be generated. + This will define the given `security.acme.certs` and set `extraDomainNames` correctly, + but does not fill any options such as credentials or dnsProvider. These have to be set + individually for each cert by the user or via `security.acme.defaults`. + ''; + }; + + options.services.nginx.virtualHosts = mkOption { + type = types.attrsOf (types.submodule (submod: { + options.useACMEWildcardHost = mkOption { + type = types.bool; + default = false; + description = mdDoc ''Automatically set useACMEHost with the correct wildcard domain for the virtualHosts's main domain.''; + }; + config = let + # This retrieves all matching wildcard certs that would include + # the corresponding domain. If no such domain is defined in + # security.acme.wildcardDomains, an assertion is triggered. + domain = submod.config._module.args.name; + matchingCerts = + filter + (x: !hasInfix "." (removeSuffix ".${x}" domain)) + config.security.acme.wildcardDomains; + in + mkIf submod.config.useACMEWildcardHost { + useACMEHost = assert assertMsg (matchingCerts != []) "No wildcard certificate was defined that matches ${domain}"; + head matchingCerts; + }; + })); + }; + + config.security.acme.certs = genAttrs config.security.acme.wildcardDomains (domain: { + extraDomainNames = ["*.${domain}"]; + }); +} diff --git a/modules/deteministic-ids.nix b/modules/system/deteministic-ids.nix similarity index 100% rename from modules/deteministic-ids.nix rename to modules/system/deteministic-ids.nix diff --git a/nix/apps/draw-graph.nix b/nix/apps/draw-graph.nix deleted file mode 100644 index eb501e0..0000000 --- a/nix/apps/draw-graph.nix +++ /dev/null @@ -1,39 +0,0 @@ -{ - self, - pkgs, - ... -}: let - inherit - (pkgs.lib) - concatStringsSep - filterAttrs - hasInfix - mapAttrsToList - ; - mapAttrsToLines = f: attrs: concatStringsSep "\n" (mapAttrsToList f attrs); - filterMapAttrsToLines = filter: f: attrs: concatStringsSep "\n" (mapAttrsToList f (filterAttrs filter attrs)); - renderNode = nodeName: node: let - renderNic = nicName: nic: '' - nic_${nicName}: ${ - if hasInfix "wlan" nicName - then "📶" - else "🖧" - } ${self.hosts.${nodeName}.physicalConnections.${nicName}} { - shape: sql_table - MAC: ${nic.matchConfig.MACAddress} - } - ''; - in '' - ${nodeName}: { - ${filterMapAttrsToLines (_: v: v.matchConfig ? MACAddress) renderNic node.config.systemd.network.networks} - } - ''; - # TODO vms - graph = '' - ${mapAttrsToLines renderNode self.colmenaNodes} - ''; -in - pkgs.writeShellScript "draw-graph" '' - set -euo pipefail - echo "${graph}" - '' diff --git a/nix/checks.nix b/nix/checks.nix index 8f512dc..4e3fd8d 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -2,8 +2,7 @@ self, pre-commit-hooks, ... -}: system: -with self.pkgs.${system}; { +}: system: { pre-commit-check = pre-commit-hooks.lib.${system}.run { diff --git a/nix/lib.nix b/nix/lib.nix index 1191f9d..8780163 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -230,7 +230,7 @@ in rec { inherit (self.nodes.${head participatingNodes}.config.lib) net; # Returns the given node's wireguard configuration of this network - wgCfgOf = node: self.nodes.${node}.config.extra.wireguard.${wgName}; + wgCfgOf = node: self.nodes.${node}.config.meta.wireguard.${wgName}; sortedPeers = peerA: peerB: if peerA < peerB @@ -261,7 +261,7 @@ in rec { # All nodes that are part of this network participatingNodes = filter - (n: builtins.hasAttr wgName self.nodes.${n}.config.extra.wireguard) + (n: builtins.hasAttr wgName self.nodes.${n}.config.meta.wireguard) (attrNames self.nodes); # Partition nodes by whether they are servers @@ -305,7 +305,7 @@ in rec { (n: filter (x: !types.isLazyValue x) (concatLists - (self.nodes.${n}.options.extra.wireguard.type.functor.wrapped.getSubOptions (wgCfgOf n)).addresses.definitions)) + (self.nodes.${n}.options.meta.wireguard.type.functor.wrapped.getSubOptions (wgCfgOf n)).addresses.definitions)) ++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes); # The cidrv4 and cidrv6 of the network spanned by all participating peer addresses.