From b0a9c77ebb20ca42bec9a830cec6133a977503f8 Mon Sep 17 00:00:00 2001 From: "Camille M. (genesis)" Date: Thu, 19 Dec 2024 14:30:33 +0100 Subject: [PATCH 1/2] fixup! chore: organizr: remove --- .../services/web-apps/organizr/default.nix | 63 ------------------- 1 file changed, 63 deletions(-) delete mode 100644 modules/services/web-apps/organizr/default.nix diff --git a/modules/services/web-apps/organizr/default.nix b/modules/services/web-apps/organizr/default.nix deleted file mode 100644 index 29508b2..0000000 --- a/modules/services/web-apps/organizr/default.nix +++ /dev/null @@ -1,63 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: - -let - cfg = config.services.organizr; -in -with lib; - -{ - options.services.organizr = { - enable = mkEnableOption "Organizr"; - - hostName = mkOption { - type = types.str; - description = "FQDN for the Organizr instance."; - }; - }; - - config = mkIf cfg.enable { - services.phpfpm.pools.organizr = { - user = "organizr"; - settings = { - "listen.owner" = config.services.nginx.user; - "pm" = "dynamic"; - "pm.max_children" = 100; - "pm.max_requests" = 500; - "pm.start_servers" = 2; - "pm.min_spare_servers" = 2; - "pm.max_spare_servers" = 5; - "php_admin_value[error_log]" = "stderr"; - "php_admin_flag[log_errors]" = true; - "catch_workers_output" = true; - }; - phpEnv."PATH" = lib.makeBinPath [ pkgs.php ]; - }; - - services.nginx.virtualHosts.${cfg.hostName} = { - root = "${pkgs.organizr}"; - locations."~ (/|\.php)$".extraConfig = '' - fastcgi_index index.php; - fastcgi_pass unix:${config.services.phpfpm.pools.organizr.socket}; - fastcgi_buffers 32 32k; - fastcgi_buffer_size 32k; - ''; - locations."/api/v2".tryFiles = "$uri /api/v2/index.php$is_args$args"; - }; - - systemd.services."phpfpm-organizr".serviceConfig.BindPaths = [ - "/var/lib/organizr/:${pkgs.organizr}/data/" - ]; - systemd.services."nginx".serviceConfig.BindPaths = [ "/var/lib/organizr/:${pkgs.organizr}/data/" ]; - - users.users.organizr = { - isSystemUser = true; - group = "organizr"; - }; - users.groups.organizr = { }; - }; -} From 374ee16c3c2b854fad7e1e89d2e59d6feef2b2b1 Mon Sep 17 00:00:00 2001 From: "Camille M. (thelonious)" Date: Tue, 3 Dec 2024 13:39:53 +0100 Subject: [PATCH 2/2] feat: machines.meta.zones --- default.nix | 3 +- deps/sources.json | 17 +- lib/dns.nix | 49 +++++ .../services/web-servers/nginx/default.nix | 116 ++++++------ profiles/base/default.nix | 176 ++++++++++-------- profiles/nsd/default.nix | 110 +++++++++++ 6 files changed, 335 insertions(+), 136 deletions(-) create mode 100644 lib/dns.nix create mode 100644 profiles/nsd/default.nix diff --git a/default.nix b/default.nix index d61493d..83a0fa8 100644 --- a/default.nix +++ b/default.nix @@ -1,7 +1,8 @@ let inputs = import ./deps; - lib = (import "${inputs.nixpkgs}/lib").extend (import ./lib inputs); + dnsLib = (import inputs.dns).lib; + lib = (import "${inputs.nixpkgs}/lib").extend (import ./lib inputs dnsLib); machines_plats = lib.lists.unique ( lib.mapAttrsToList (_name: value: value.system) ( diff --git a/deps/sources.json b/deps/sources.json index a4dc0de..21e9c0d 100644 --- a/deps/sources.json +++ b/deps/sources.json @@ -48,6 +48,21 @@ "url": "https://github.com/nix-community/disko/archive/a08bfe06b39e94eec98dd089a2c1b18af01fef19.tar.gz", "hash": "0m9w7yld1sagyv6rn880qqggy1zm891yh2c50lsksdklbahbrcbg" }, + "dns": { + "type": "GitRelease", + "repository": { + "type": "GitHub", + "owner": "nix-community", + "repo": "dns.nix" + }, + "pre_releases": false, + "version_upper_bound": null, + "release_prefix": null, + "version": "v1.2.0", + "revision": "a3196708a56dee76186a9415c187473b94e6cbae", + "url": "https://api.github.com/repos/nix-community/dns.nix/tarball/v1.2.0", + "hash": "011b6ahj4qcf7jw009qgbf6k5dvjmgls88khwzgjr9kxlgbypb90" + }, "git-hooks": { "type": "Git", "repository": { @@ -208,4 +223,4 @@ } }, "version": 3 -} \ No newline at end of file +} diff --git a/lib/dns.nix b/lib/dns.nix new file mode 100644 index 0000000..636dea1 --- /dev/null +++ b/lib/dns.nix @@ -0,0 +1,49 @@ +{ + lib, + dnsLib, + ... +}: +let + SOA = { + nameServer = "ns"; + adminEmail = "dns@mondon.me"; + serial = 0; + }; + NS = [ + "ns1" + "ns2" + ]; + + # Set some defaults for a zone + getSubmodulesCustom = + inputs@{ name, ... }: + lib.recursiveUpdate ((lib.head dnsLib.types.zone.getSubModules) ({ inherit name; } // inputs)) { + config = { + SOA = lib.mkDefault SOA; + NS = lib.mkDefault NS; + subdomains = { + ns1 = lib.mkDefault { + A = [ lib.snowfield.router.ips.public.ipv4 ]; + AAAA = [ lib.snowfield.router.ips.public.ipv6 ]; + }; + ns2 = lib.mkDefault { + A = [ lib.snowfield.akhaten.ips.public.ipv4 ]; + AAAA = [ lib.snowfield.akhaten.ips.public.ipv6 ]; + }; + }; + }; + }; + +in +with lib; +{ + options = { + machine.meta.zones = mkOption { + type = types.attrsOf ( + recursiveUpdate dnsLib.types.zone { getSubModules = [ getSubmodulesCustom ]; } + ); + default = { }; + }; + }; + +} diff --git a/modules/services/web-servers/nginx/default.nix b/modules/services/web-servers/nginx/default.nix index 33e596d..4f20013 100755 --- a/modules/services/web-servers/nginx/default.nix +++ b/modules/services/web-servers/nginx/default.nix @@ -1,29 +1,26 @@ { config, lib, ... }: +with lib; let cfg = config.services.nginx; + + mergeSub = f: mkMerge (map (sub: f (sub.systemConfig systemArgs)) (attrValues cfg.virtualHosts)); + + recordsFromDomain = + domain: + mapAttrs' ( + n: v: + nameValuePair (dns.domainToZone dns.allowedDomains n) ( + let + subdomain = dns.getDomainPrefix dns.allowedDomains n; + in + if elem subdomain dns.allowedDomains then v else { subdomains."${subdomain}" = v; } + ) + ) (dns.domainToRecords domain config.machine.meta (dns.isVPNDomain domain)); in -with lib; { options.services.nginx = { - noDefault.enable = mkEnableOption ''Don't fallback to default page''; - - publicDomains = mkOption { - default = [ "mondon.xyz" ]; - type = types.listOf types.str; - }; - - vpnDomains = mkOption { - default = [ ".kms" ]; - type = types.listOf types.str; - }; - - vpnAcmeServer = mkOption { - default = "https://ca.luj/acme/acme/directory"; - type = types.str; - }; - localDomains = mkOption { default = [ ".lan" @@ -32,23 +29,36 @@ with lib; type = types.listOf types.str; }; + noDefault.enable = mkEnableOption ''Don't fallback to default page''; + + publicDomains = mkOption { type = types.listOf types.str; }; + + vpnAcmeServer = mkOption { type = types.str; }; + + vpnDomains = mkOption { type = types.listOf types.str; }; + virtualHosts = mkOption { type = types.attrsOf ( types.submodule ( { name, config, - publicDomains, ... }: { - options.port = mkOption { - type = types.port; - default = 0; - }; - options.websockets = mkOption { - type = types.bool; - default = false; + options = { + port = mkOption { + type = types.port; + default = 0; + }; + websockets = mkOption { + type = types.bool; + default = false; + }; + systemConfig = mkOption { + internal = true; + type = types.unspecified; # A function from module arguments to config. + }; }; config = { @@ -60,21 +70,28 @@ with lib; p = config.port; in mkIf (p != 0) (mkDefault "http://127.0.0.1:${toString p}"); + proxyWebsockets = mkDefault config.websockets; + + # Firewall VPN domains + extraConfig = mkIf (hasSuffixIn cfg.vpnDomains name) '' + allow 100.100.45.0/24; + allow fd7a:115c:a1e0::/48; + deny all; + ''; + }; + + extraConfig = mkIf (hasSuffixIn cfg.vpnDomains name) '' + ssl_stapling off; + ''; + + systemConfig = _: { + machine.meta.zones = optionalAttrs (name != "default") (recordsFromDomain name); + + security.acme.certs = optionalAttrs (hasSuffixIn cfg.vpnDomains name) { + "${name}".server = mkIf (hasSuffixIn cfg.vpnDomains name) cfg.vpnAcmeServer; + }; }; - # Firewall VPN domains - extraConfig = - if (hasSuffixIn cfg.publicDomains name) then - '' - allow all; - '' - else - '' - if ($bad_ip) { - return 444; - } - ssl_stapling off; - ''; }; } ) @@ -89,18 +106,7 @@ with lib; recommendedGzipSettings = mkDefault true; recommendedTlsSettings = mkDefault true; - # VPN IPs appendHttpConfig = '' - geo $bad_ip { - default 1; - 127.0.0.1/32 0; - ::1/128 0; - 192.168.0.0/16 0; - fc00::/7 0; - 100.100.45.0/24 0; - fd7a:115c:a1e0::/48 0; - } - proxy_headers_hash_max_size 512; proxy_headers_hash_bucket_size 128; ''; @@ -115,18 +121,14 @@ with lib; sslCertificate = "/var/lib/acme/default/cert.pem"; sslCertificateKey = "/var/lib/acme/default/key.pem"; extraConfig = '' + ssl_stapling off; return 444; ''; }; }; - # Use VPN CA only on VPN domains - security.acme.certs = mapAttrs ( - n: _: - mkIf (config.services.tailscale.enable && hasSuffixIn cfg.vpnDomains n && !hasPrefix "www." n) { - server = mkDefault cfg.vpnAcmeServer; - } - ) cfg.virtualHosts; + machine = mergeSub (c: c.machine); + security.acme.certs = mergeSub (c: c.security.acme.certs); # Open port 443 only if necessary networking.firewall.allowedTCPPorts = mkIf cfg.enable [ diff --git a/profiles/base/default.nix b/profiles/base/default.nix index 5c4a41b..85cceb0 100644 --- a/profiles/base/default.nix +++ b/profiles/base/default.nix @@ -16,55 +16,65 @@ let in { - users = { - defaultUserShell = pkgs.fish; + console.keyMap = "fr"; - users = { - camille = { - isNormalUser = true; - description = "Camille"; - extraGroups = [ - "audio" - "lp" - "media" - "networkmanager" - "pipewire" - "scanner" - "wheel" - ]; - openssh.authorizedKeys.keys = sshPubKeys; - }; + deployment.buildOnTarget = mkDefault true; - root = { - extraGroups = [ - "audio" - "pulse-access" - ]; - openssh.authorizedKeys.keys = sshPubKeys; - }; + environment.systemPackages = with pkgs; [ + attic-client + comma-with-db + dig + direnv + du-dust + htop + lazygit + lsof + neofetch + nix-init + nix-output-monitor + nix-tree + nix-update + nixos-option + nmap + ntfs3g + oh-my-fish + powertop + tldr + unzip + wget + zip + ]; + + i18n = { + defaultLocale = "fr_FR.UTF-8"; + extraLocaleSettings = { + LANG = "fr_FR.UTF-8"; + LC_ADDRESS = "fr_FR.UTF-8"; + LC_ALL = "fr_FR.UTF-8"; + LC_CTYPE = "fr_FR.UTF-8"; + LC_IDENTIFICATION = "fr_FR.UTF-8"; + LC_MEASUREMENT = "fr_FR.UTF-8"; + LC_MESSAGES = "fr_FR.UTF-8"; + LC_MONETARY = "fr_FR.UTF-8"; + LC_NAME = "fr_FR.UTF-8"; + LC_NUMERIC = "fr_FR.UTF-8"; + LC_PAPER = "fr_FR.UTF-8"; + LC_TELEPHONE = "fr_FR.UTF-8"; + LC_TIME = "fr_FR.UTF-8"; }; }; - deployment.buildOnTarget = mkDefault true; - - console.keyMap = "fr"; - time.timeZone = mkOverride 500 "Europe/Paris"; - i18n.defaultLocale = "fr_FR.UTF-8"; - i18n.extraLocaleSettings = { - LANG = "fr_FR.UTF-8"; - LC_ADDRESS = "fr_FR.UTF-8"; - LC_ALL = "fr_FR.UTF-8"; - LC_CTYPE = "fr_FR.UTF-8"; - LC_IDENTIFICATION = "fr_FR.UTF-8"; - LC_MEASUREMENT = "fr_FR.UTF-8"; - LC_MESSAGES = "fr_FR.UTF-8"; - LC_MONETARY = "fr_FR.UTF-8"; - LC_NAME = "fr_FR.UTF-8"; - LC_NUMERIC = "fr_FR.UTF-8"; - LC_PAPER = "fr_FR.UTF-8"; - LC_TELEPHONE = "fr_FR.UTF-8"; - LC_TIME = "fr_FR.UTF-8"; - }; + machine.meta.zones.kms = + lib.mkIf + (lib.hasAttrByPath [ + "vpn" + "ipv4" + ] config.machine.meta.ips) + { + subdomains.${config.networking.hostName} = { + A = [ config.machine.meta.ips.vpn.ipv4 ]; + }; + }; programs = { git = { @@ -103,6 +113,14 @@ in }; }; + security = { + acme = { + defaults.email = "camillemondon@free.fr"; + acceptTerms = true; + }; + pki.certificateFiles = [ ./saumonnet.crt ]; + }; + services = { eternal-terminal.enable = true; openssh.settings.PasswordAuthentication = mkDefault false; @@ -120,43 +138,47 @@ in dnssec = "allow-downgrade"; dnsovertls = "opportunistic"; }; - }; - networking = { - firewall.checkReversePath = mkIf config.services.tailscale.enable "loose"; - nftables.enable = true; + networking = { + firewall.checkReversePath = mkIf config.services.tailscale.enable "loose"; + nftables.enable = true; + }; + + nginx = { + publicDomains = [ "mondon.xyz" ]; + vpnDomains = [ ".kms" ]; + vpnAcmeServer = "https://ca.luj/acme/acme/directory"; + }; }; - environment.systemPackages = with pkgs; [ - attic-client - comma-with-db - dig - direnv - du-dust - htop - lazygit - lsof - neofetch - nix-init - nix-output-monitor - nix-tree - nix-update - nixos-option - nmap - ntfs3g - oh-my-fish - powertop - tldr - unzip - wget - zip - ]; + time.timeZone = mkOverride 500 "Europe/Paris"; - security = { - acme = { - defaults.email = "camillemondon@free.fr"; - acceptTerms = true; + users = { + defaultUserShell = pkgs.fish; + + users = { + camille = { + isNormalUser = true; + description = "Camille"; + extraGroups = [ + "audio" + "lp" + "media" + "networkmanager" + "pipewire" + "scanner" + "wheel" + ]; + openssh.authorizedKeys.keys = sshPubKeys; + }; + + root = { + extraGroups = [ + "audio" + "pulse-access" + ]; + openssh.authorizedKeys.keys = sshPubKeys; + }; }; - pki.certificateFiles = [ ./saumonnet.crt ]; }; } diff --git a/profiles/nsd/default.nix b/profiles/nsd/default.nix new file mode 100644 index 0000000..e13ce63 --- /dev/null +++ b/profiles/nsd/default.nix @@ -0,0 +1,110 @@ +{ + config, + lib, + nixosConfigurations, + dnsLib, + ... +}: +let + zonesToList = lib.mapAttrsToList (name: value: { ${name} = value; }); + zonesFromConfig = lib.mkMerge ( + lib.fold (elem: acc: acc ++ (zonesToList elem.config.machine.meta.zones)) [ ] ( + lib.attrValues nixosConfigurations + ) + ); + + allowedDomains = [ + "camillemondon.com" + "camillemondon.fr" + "ceciliaflamenca.com" + "kms" + "mondon.xyz" + "saumon.network" + "varanda.fr" + "yali.es" + ]; + + isVPNDomain = domain: lib.dns.domainToZone [ "kms" ] domain != null; + + zonesFromSnowField = lib.fold (elem: acc: lib.attrsets.recursiveUpdate acc elem) { } ( + lib.flatten ( + map ( + elem: + let + domains = if builtins.hasAttr "subdomains" elem then elem.subdomains else [ ]; + in + map (domain: { + machine.meta.zones.${lib.dns.domainToZone allowedDomains domain}.subdomains = + lib.dns.domainToRecords (lib.dns.getDomainPrefix allowedDomains domain) elem + (isVPNDomain domain); + }) domains + + ) (lib.attrValues lib.snowfield) + ) + ); + + evalZones = + zones: + (lib.evalModules { + modules = [ + { + options = { + zones = lib.mkOption { + type = lib.types.attrsOf dnsLib.types.zone; + description = "DNS zones"; + }; + }; + config = { + inherit zones; + }; + } + ]; + }).config.zones; + + stateDir = "/var/lib/nsd"; + +in + +lib.mkMerge [ + { + services.nsd = { + enable = true; + interfaces = [ + config.machine.meta.ips.vpn.ipv4 + config.machine.meta.ips.vpn.ipv6 + config.machine.meta.ips.public.ipv6 + ]; + zones = lib.mapAttrs (_: value: { + data = builtins.toString value; + provideXFR = [ + "100.100.45.0/24 NOKEY" + "fd7a:115c:a1e0::1/128 NOKEY" + ]; + notify = [ + "${lib.snowfield.akhaten.ips.vpn.ipv4} NOKEY" + "fd7a:115c:a1e0::1 NOKEY" + ]; + }) (evalZones zonesFromConfig); + }; + + systemd.services.nsd.preStart = lib.mkAfter '' + if [ -f ${stateDir}/counter ]; then + current_value=$(cat ${stateDir}/counter) + new_value=$((current_value + 1)) + echo "$new_value" > ${stateDir}/counter + else + echo "0" > ${stateDir}/counter + new_value="0" + fi + for file in ${stateDir}/zones/*; do + sed -i "3s/0/$new_value/" "$file" + done + ''; + + networking.firewall.allowedUDPPorts = [ 53 ]; + + } + + # DNS Records from all non local configurations are exported here + zonesFromSnowField +]