From f7b1c750aa6d464a8281514bfa4871e8ecba80e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charlotte=20=F0=9F=A6=9D=20Delenk?= Date: Sun, 24 Apr 2022 21:20:17 +0100 Subject: [PATCH] add hostapd support --- config/nas.nix | 1 + config/services/hostapd.nix | 17 +++ modules/hostapd.nix | 251 ++++++++++++++++++++++++++++++++++++ secrets/nas.yaml | 5 +- zones/int.chir.rs.nix | 74 +++++++++-- 5 files changed, 335 insertions(+), 13 deletions(-) create mode 100644 config/services/hostapd.nix create mode 100644 modules/hostapd.nix diff --git a/config/nas.nix b/config/nas.nix index ecfa4244..e65de8ae 100644 --- a/config/nas.nix +++ b/config/nas.nix @@ -12,6 +12,7 @@ nixos-hardware.nixosModules.common-cpu-amd nixos-hardware.nixosModules.common-gpu-nvidia nixos-hardware.nixosModules.common-pc-hdd + ./services/hostapd.nix ]; hardware.cpu.amd.updateMicrocode = true; diff --git a/config/services/hostapd.nix b/config/services/hostapd.nix new file mode 100644 index 00000000..5c7db121 --- /dev/null +++ b/config/services/hostapd.nix @@ -0,0 +1,17 @@ +{ config, ... }: { + imports = [ + ../../modules/hostapd.nix + ]; + services.hostapd = { + countryCode = "DE"; + interface = "wlp6s0"; + ssid = "🦝"; + wpa = true; + wpaPassphraseFile = config.sops.secrets."services/hostapd".path; + }; + sops.secrets."services/hostapd" = { + restartUnits = [ + "hostapd.service" + ]; + }; +} diff --git a/modules/hostapd.nix b/modules/hostapd.nix new file mode 100644 index 00000000..fdcc7647 --- /dev/null +++ b/modules/hostapd.nix @@ -0,0 +1,251 @@ +{ config, lib, pkgs, utils, ... }: + +# TODO: +# +# asserts +# ensure that the nl80211 module is loaded/compiled in the kernel +# wpa_supplicant and hostapd on the same wireless interface doesn't make any sense + +with lib; + +let + + cfg = config.services.hostapd; + + escapedInterface = utils.escapeSystemdPath cfg.interface; + + configFile = pkgs.writeText "hostapd.conf" '' + interface=${cfg.interface} + driver=${cfg.driver} + ssid=${cfg.ssid} + hw_mode=${cfg.hwMode} + channel=${toString cfg.channel} + ${optionalString (cfg.countryCode != null) "country_code=${cfg.countryCode}"} + ${optionalString (cfg.countryCode != null) "ieee80211d=1"} + + # logging (debug level) + logger_syslog=-1 + logger_syslog_level=${toString cfg.logLevel} + logger_stdout=-1 + logger_stdout_level=${toString cfg.logLevel} + + ctrl_interface=/run/hostapd + ctrl_interface_group=${cfg.group} + + ${optionalString cfg.wpa '' + wpa=2 + wpa_passphrase=${if cfg.wpaPassphrase then cfg.wpaPassphrase else "#WPA_PASSPHRASE#"} + ''} + ${optionalString cfg.noScan "noscan=1"} + + ${cfg.extraConfig} + ''; + +in + +{ + ###### interface + + options = { + + services.hostapd = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable putting a wireless interface into infrastructure mode, + allowing other wireless devices to associate with the wireless + interface and do wireless networking. A simple access point will + , + , and + , as well as DHCP on the wireless + interface to provide IP addresses to the associated stations, and + NAT (from the wireless interface to an upstream interface). + ''; + }; + + interface = mkOption { + default = ""; + example = "wlp2s0"; + type = types.str; + description = '' + The interfaces hostapd will use. + ''; + }; + + noScan = mkOption { + type = types.bool; + default = false; + description = '' + Do not scan for overlapping BSSs in HT40+/- mode. + Caution: turning this on will violate regulatory requirements! + ''; + }; + + driver = mkOption { + default = "nl80211"; + example = "hostapd"; + type = types.str; + description = '' + Which driver hostapd will use. + Most applications will probably use the default. + ''; + }; + + ssid = mkOption { + default = "nixos"; + example = "mySpecialSSID"; + type = types.str; + description = "SSID to be used in IEEE 802.11 management frames."; + }; + + hwMode = mkOption { + default = "g"; + type = types.enum [ "a" "b" "g" ]; + description = '' + Operation mode. + (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g). + ''; + }; + + channel = mkOption { + default = 7; + example = 11; + type = types.int; + description = '' + Channel number (IEEE 802.11) + Please note that some drivers do not use this value from + hostapd and the channel will need to be configured + separately with iwconfig. + ''; + }; + + group = mkOption { + default = "wheel"; + example = "network"; + type = types.str; + description = '' + Members of this group can control hostapd. + ''; + }; + + wpa = mkOption { + type = types.bool; + default = true; + description = '' + Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point. + ''; + }; + + wpaPassphrase = mkOption { + default = null; + example = "any_64_char_string"; + type = types.nullOr types.str; + description = '' + WPA-PSK (pre-shared-key) passphrase. Clients will need this + passphrase to associate with this access point. + Warning: This passphrase will get put into a world-readable file in + the Nix store! + ''; + }; + + wpaPassphraseFile = mkOption { + default = null; + example = "/run/secrets/wpa_passphrase"; + type = types.nullOr types.str; + description = '' + File containing WPA-PSK passphrase. Clients will need this + passphrase to associate with this access point. + ''; + }; + + logLevel = mkOption { + default = 2; + type = types.int; + description = '' + Levels (minimum value for logged events): + 0 = verbose debugging + 1 = debugging + 2 = informational messages + 3 = notification + 4 = warning + ''; + }; + + countryCode = mkOption { + default = null; + example = "US"; + type = with types; nullOr str; + description = '' + Country code (ISO/IEC 3166-1). Used to set regulatory domain. + Set as needed to indicate country in which device is operating. + This can limit available channels and transmit power. + These two octets are used as the first two octets of the Country String + (dot11CountryString). + If set this enables IEEE 802.11d. This advertises the countryCode and + the set of allowed channels and transmit power levels based on the + regulatory limits. + ''; + }; + + extraConfig = mkOption { + default = ""; + example = '' + auth_algo=0 + ieee80211n=1 + ht_capab=[HT40-][SHORT-GI-40][DSSS_CCK-40] + ''; + type = types.lines; + description = "Extra configuration options to put in hostapd.conf."; + }; + }; + }; + + disabledModules = [ "services/networking/hostapd.nix" ]; + + ###### implementation + + config = mkIf cfg.enable { + + assertions = [ + { + assertion = cfg.wpa != null -> (cfg.wpaPassphrase != null || cfg.wpaPassphraseFile != null); + message = "Either wpaPassphrase or wpaPassphraseFile must be set if wpa is enabled."; + } + { + assertion = cfg.wpaPassphraseFile != null -> cfg.wpaPassphrase == null; + message = "You cannot provide a wpaPassphrase and a wpaPassphraseFile!"; + } + ]; + + environment.systemPackages = [ pkgs.hostapd ]; + + services.udev.packages = optional (cfg.countryCode != null) [ pkgs.crda ]; + + systemd.services.hostapd = + { + description = "hostapd wireless AP"; + + path = [ pkgs.hostapd ]; + after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ]; + bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ]; + requiredBy = [ "network-link-${cfg.interface}.service" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = mkIf cfg.wpaPassphraseFile != null '' + PASSPHRASE=$(cat ${cfg.wpaPassphraseFile}) + sed 's|#WPA_PASSPHRASE#|$PASSPHRASE|g' ${configFile} > /run/hostapd/hostapd.conf + ''; + + serviceConfig = + { + ExecStart = "${pkgs.hostapd}/bin/hostapd ${if cfg.wpaPassphraseFile != null then "/run/hostapd/hostapd.conf" else configFile}"; + Restart = "always"; + }; + }; + systemd.tmpfiles.rules = mkIf cfg.wpaPassphraseFile != null [ + "d '/run/hostapd' 0700 root root - -" + ]; + }; +} diff --git a/secrets/nas.yaml b/secrets/nas.yaml index 15dd2f79..beb51d93 100644 --- a/secrets/nas.yaml +++ b/secrets/nas.yaml @@ -6,6 +6,7 @@ services: gitea_token: ENC[AES256_GCM,data:v0Ej8841I1F/dK5ZplRzZlvngpueMQKspM5USzX9VkOEmpCs2NA3+Q==,iv:fZisAuyqk7ATFx6qHYkScUeS8SsikjiPzVovZjGnUYM=,tag:7+O+Sn7unPDy88a6T70Jmg==,type:str] github_token: ENC[AES256_GCM,data:AWMeX+P8YHGpSuH+5KqvE9zNxkEPKGvdRaQjNysO4/XE4csGjCvmjA==,iv:MCRtws/SM7lWS2/2pp5tbeX7+I5h4LVd9bJp//ln9hs=,tag:LMEGWFAaOqH0fqfNgc87AQ==,type:str] aws_credentials: ENC[AES256_GCM,data:yxJU6d6BMi+LHUPimMkgr5h6accGXQXxFu9A0swdwKII/Xfo4ALAw4J4aEhpnNuK8JwmzuuDdTDGnilzuEATeaANa2cNXps6AWw8Hem8idw585xTcU1YBEOdbBSs/mKK6S+Da1OU5jC1atrCCWY7cg==,iv:tAEGsniZ7N/jBp7btLlD1pNcF4NvEmpO6zXji1H29t8=,tag:lmAB3QMfaT3ljDmr+8IBHA==,type:str] + hostapd: ENC[AES256_GCM,data:XDLRbra/RadQE/t6cPG94A==,iv:EafhBPWviYaZLodGcIDTfxUoLnnaLQKyWLeSwZe9d0Q=,tag:yvfqt74qR/en/HoW2HmRKQ==,type:int] security: restic: password: ENC[AES256_GCM,data:n+M6pfe0YrONaYo3HSnijHxhThg=,iv:0J2t+58tYRJD1GmnJa8w30U+RwOl67eWeHhvLk0eeks=,tag:ivuZqpGrU7ZHFZ4IiMvxBw==,type:str] @@ -36,8 +37,8 @@ sops: WnV3QWxtalIzWFdoQmpDTmJsNGdNOW8K++rFGXy0G6Gcu2gQwSP6xfXInQ/y5nh5 2oGp8sfOLFWnNI4SWL0ChP47K3C/9ysUHwQnUYPbRafZ/4X6cN40ZQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2022-04-24T10:49:24Z" - mac: ENC[AES256_GCM,data:gSBsJnw13ic3ysf+FevyXi3zhyfld5aChTRa49OE65FcedbqqToctALLUo4UXZbbPonu3ixBV4R297NDjWFlS+3QFR15uXMZpbHcVSLPtDF+TTx4c90MA+V2qrfTqPGaBb8zBqGOHqIOYBNEQWfjirYALnguvTBFiYUoGE9nswE=,iv:NYc+4/SLz92dYP+zdwW3WTpB5wyeTQ2SfpWLWP16HoY=,tag:qJ0AF8RtPpwJ+dNPlo+jNA==,type:str] + lastmodified: "2022-04-24T20:20:10Z" + mac: ENC[AES256_GCM,data:qANlJ5sFKi6OE6ZaI0mUYHkb1UvJN9cxIlZ213dN9vtOa3eaNAHqW8SHa3Gt1lSjgoY9ux0SoaDb4Pyevq2COVm2dHlHPtfhCzvpVpjy3xLI7Q69OFLRgKxUQj2ZEtXWKy7ea82EVSTEWavlDaXzd/lpaiKugYgM/xwQjzbifgQ=,iv:Oc+SPuQKA1awYDZwdQgHWbVGJFRbTcWPveNNlM3b7y0=,tag:Z4s44hHbdL4mwFaKPq9lmQ==,type:str] pgp: - created_at: "2022-04-24T10:34:20Z" enc: | diff --git a/zones/int.chir.rs.nix b/zones/int.chir.rs.nix index 344ea509..7b8577ef 100644 --- a/zones/int.chir.rs.nix +++ b/zones/int.chir.rs.nix @@ -8,7 +8,7 @@ in SOA = { nameServer = "ns1.chir.rs."; adminEmail = "lotte@chir.rs"; - serial = 9; + serial = 10; }; NS = [ "ns1.chir.rs." @@ -38,14 +38,6 @@ in (ttl zoneTTL (aaaa "fd00:e621:e621::1")) ]; }; - nas = { - A = [ - (ttl zoneTTL (a "10.0.2.2")) - ]; - AAAA = [ - (ttl zoneTTL (aaaa "fd00:e621:e621:2::2")) - ]; - }; nixos-8gb-fsn1-1 = { AAAA = [ (ttl zoneTTL (aaaa "fd0d:a262:1fa6:e621:b4e1:8ff:e658:6f49")) @@ -187,13 +179,73 @@ in } ]; }; + nas = { + AAAA = [ + (ttl zoneTTL (aaaa "fd0d:a262:1fa6:e621:bc9b:6a33:86e4:873b")) + ]; + SSHFP = [ + { + algorithm = "rsa"; + mode = "sha1"; + fingerprint = "13e1173d96b822c98a7b3cd47be2e830f7758671"; + ttl = zoneTTL; + } + { + algorithm = "rsa"; + mode = "sha256"; + fingerprint = "2e87a3fd00918e4f1e47d3b14b59e846ee016a0d3269cb2524c8d28b121e130e"; + ttl = zoneTTL; + } + { + algorithm = "ed25519"; + mode = "sha1"; + fingerprint = "d1df2d244980a5e4dde37eed678b59a2239ca2ac"; + ttl = zoneTTL; + } + { + algorithm = "ed25519"; + mode = "sha256"; + fingerprint = "33d6c993ee3789fb6a2e60c243da7095eb79ce8e522b087f8a31ea400d7b034e"; + ttl = zoneTTL; + } + ]; + # TODO: add TLSA + HTTPS = [ + { + svcPriority = 1; + targetName = "."; + alpn = [ "http/1.1" "h2" "h3" ]; + ipv6hint = [ "fd0d:a262:1fa6:e621:bc9b:6a33:86e4:873b" ]; + ttl = zoneTTL; + } + ]; + CAA = [ + { + issuerCritical = false; + tag = "issue"; + value = "letsencrypt.org"; + ttl = zoneTTL; + } + { + issuerCritical = false; + tag = "issuewild"; + value = "letsencrypt.org"; + ttl = zoneTTL; + } + { + issuerCritical = false; + tag = "iodef"; + value = "mailto:lotte@chir.rs"; + ttl = zoneTTL; + } + ]; + }; grafana.CNAME = [ "nixos-8gb-fsn1-1" ]; minio.CNAME = [ "nixos-8gb-fsn1-1" ]; minio-console.CNAME = [ "nixos-8gb-fsn1-1" ]; backup.CNAME = [ "nas" ]; - cache.CNAME = [ "nutty-noon" ]; - hydra.CNAME = [ "nutty-noon" ]; + hydra.CNAME = [ "nas" ]; _acme-challenge = delegateTo [ "ns1.chir.rs." "ns2.chir.rs."