From cdd8b9cc12705a657cf736b35c63634e66a8d383 Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Sat, 7 Dec 2024 16:20:53 +0100 Subject: [PATCH] feat(machines): add ap01 initial configuration Signed-off-by: Ryan Lahfa --- .forgejo/workflows/eval-nodes.yaml | 11 ++++ machines/ap01/_configuration.nix | 39 +++++++++++++ machines/ap01/addresses.nix | 18 ++++++ machines/ap01/dns.nix | 30 ++++++++++ machines/ap01/ipc.nix | 8 +++ machines/ap01/lan.nix | 39 +++++++++++++ machines/ap01/management.nix | 12 ++++ machines/ap01/metadata.nix | 15 +++++ machines/ap01/recovery.nix | 45 +++++++++++++++ machines/ap01/system.nix | 28 +++++++++ machines/ap01/wlan.nix | 93 ++++++++++++++++++++++++++++++ meta/nodes.nix | 10 ++++ 12 files changed, 348 insertions(+) create mode 100644 machines/ap01/_configuration.nix create mode 100644 machines/ap01/addresses.nix create mode 100644 machines/ap01/dns.nix create mode 100644 machines/ap01/ipc.nix create mode 100644 machines/ap01/lan.nix create mode 100644 machines/ap01/management.nix create mode 100644 machines/ap01/metadata.nix create mode 100644 machines/ap01/recovery.nix create mode 100644 machines/ap01/system.nix create mode 100644 machines/ap01/wlan.nix diff --git a/.forgejo/workflows/eval-nodes.yaml b/.forgejo/workflows/eval-nodes.yaml index 0b3ef71..d9ad824 100644 --- a/.forgejo/workflows/eval-nodes.yaml +++ b/.forgejo/workflows/eval-nodes.yaml @@ -1,4 +1,15 @@ jobs: + ap01: + runs-on: nix + steps: + - uses: actions/checkout@v3 + - env: + BUILD_NODE: ap01 + STORE_ENDPOINT: https://tvix-store.dgnum.eu/infra-signing/ + STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }} + STORE_USER: admin + name: Build and cache ap01 + run: nix-shell -A eval-nodes --run cache-node bridge01: runs-on: nix steps: diff --git a/machines/ap01/_configuration.nix b/machines/ap01/_configuration.nix new file mode 100644 index 0000000..e959da6 --- /dev/null +++ b/machines/ap01/_configuration.nix @@ -0,0 +1,39 @@ +{ + modulesPath, + ... +}: +{ + imports = [ + "${modulesPath}/wlan.nix" + "${modulesPath}/network" + "${modulesPath}/hostapd" + "${modulesPath}/ssh" + "${modulesPath}/ntp" + "${modulesPath}/vlan" + "${modulesPath}/bridge" + "${modulesPath}/jitter-rng" + "${modulesPath}/pki" + "${modulesPath}/ubus" + ../../modules/dgn-access-control.nix + # System-level configuration + ./system.nix + # Configures our own WLAN. + ./wlan.nix + # Configures our LAN interfaces, e.g. bridge + VLANs. + ./lan.nix + # Configures our IPv4/IPv6 addresses, e.g. DHCPv4 on VLAN 0, SLAAC on VLAN 3001. + ./addresses.nix + # Configures a basic local DNS. + ./dns.nix + # Configures our management layer, e.g. SSH server + DGNum FAI keys. + ./management.nix + # Configures our recovery system, e.g. a levitation script. + ./recovery.nix + # Metadata on the system for field recovery. + ./metadata.nix + # TODO: god that's so a fucking hack. + (import "${modulesPath}/../devices/zyxel-nwa50ax").module + ]; + + hostname = "ap01-prototype"; +} diff --git a/machines/ap01/addresses.nix b/machines/ap01/addresses.nix new file mode 100644 index 0000000..b3d0840 --- /dev/null +++ b/machines/ap01/addresses.nix @@ -0,0 +1,18 @@ +{ config, ... }: +let + svc = config.system.service; +in +{ + services.dhcpv4 = svc.network.dhcp.client.build { + interface = config.services.int; + dependencies = [ + config.services.bridge.components.lan + ]; + }; + + services.defaultroute4 = svc.network.route.build { + via = "$(output ${config.services.dhcpv4} router)"; + target = "default"; + dependencies = [ config.services.dhcpv4 ]; + }; +} diff --git a/machines/ap01/dns.nix b/machines/ap01/dns.nix new file mode 100644 index 0000000..d8346f8 --- /dev/null +++ b/machines/ap01/dns.nix @@ -0,0 +1,30 @@ +{ config, pkgs, ... }: +let + inherit (pkgs.liminix.services) oneshot; + inherit (pkgs.pseudofile) dir symlink; + inherit (pkgs) serviceFns; +in +{ + # TODO: support dynamic reconfiguration once we are in the target VLAN? + services.resolvconf = oneshot rec { + name = "resolvconf"; + up = '' + . ${serviceFns} + ( in_outputs ${name} + for i in $(output ${config.services.dhcpv4} dns); do + echo "nameserver $i" >> resolv.conf + done + ) + ''; + + dependencies = [ + config.services.dhcpv4 + ]; + }; + + filesystem = dir { + etc = dir { + "resolv.conf" = symlink "${config.services.resolvconf}/.outputs/resolv.conf"; + }; + }; +} diff --git a/machines/ap01/ipc.nix b/machines/ap01/ipc.nix new file mode 100644 index 0000000..4742da7 --- /dev/null +++ b/machines/ap01/ipc.nix @@ -0,0 +1,8 @@ +{ config, ... }: +let + svc = config.system.service; +in +{ + # ubus socket for various needs. + services.ubus = svc.ubus.build { }; +} diff --git a/machines/ap01/lan.nix b/machines/ap01/lan.nix new file mode 100644 index 0000000..4916706 --- /dev/null +++ b/machines/ap01/lan.nix @@ -0,0 +1,39 @@ +{ config, ... }: +let + svc = config.system.service; +in +{ + services.int = svc.bridge.primary.build { + ifname = "int"; + macAddressFromInterface = config.hardware.networkInterfaces.lan; + }; + + services.bridge = svc.bridge.members.build { + primary = config.services.int; + members = { + lan.member = config.hardware.networkInterfaces.lan; + wlan0 = { + member = config.hardware.networkInterfaces.wlan0; + # Bridge only once hostapd is ready. + dependencies = [ config.services.hostap-1-ready ]; + }; + wlan1 = { + member = config.hardware.networkInterfaces.wlan1; + # Bridge only once hostapd is ready. + dependencies = [ config.services.hostap-2-ready ]; + }; + }; + }; + + # Default VLAN + # services.vlan-apro = svc.vlan.build { + # vlanId = 0; + # interface = config.services.int; + # }; + + # # Administration VLAN + # services.vlan-admin = svc.vlan.build { + # vlan = 3001; + # interface = config.services.int; + # }; +} diff --git a/machines/ap01/management.nix b/machines/ap01/management.nix new file mode 100644 index 0000000..4c5b2ab --- /dev/null +++ b/machines/ap01/management.nix @@ -0,0 +1,12 @@ +{ config, ... }: +let + svc = config.system.service; +in +{ + # SSH keys are handled by the access control module. + dgn-access-control.enable = true; + users.root = { + passwd = "$6$Z2MiaMXkpUJRPl2/$fxVE3iD/n208CISM2F6OnWj0Qq0QG2tTQqLCjU80PFJJGIwNLLyOp6SeYH3dH20OvJX1loZRETrThZfIPw.rb/"; + }; + services.sshd = svc.ssh.build { allowRoot = true; }; +} diff --git a/machines/ap01/metadata.nix b/machines/ap01/metadata.nix new file mode 100644 index 0000000..225cac8 --- /dev/null +++ b/machines/ap01/metadata.nix @@ -0,0 +1,15 @@ +{ pkgs, ... }: +let + inherit (pkgs.pseudofile) dir; +in +{ + filesystem = dir { + etc = dir { + "nixpkgs.version" = { + type = "f"; + file = "${pkgs.lib.version}"; + mode = "0444"; + }; + }; + }; +} diff --git a/machines/ap01/recovery.nix b/machines/ap01/recovery.nix new file mode 100644 index 0000000..38271f3 --- /dev/null +++ b/machines/ap01/recovery.nix @@ -0,0 +1,45 @@ +{ + config, + pkgs, + modulesPath, + ... +}: +let + svc = config.system.service; +in +{ + defaultProfile.packages = with pkgs; [ + # Levitate enable us to mass-reinstall the system on the fly. + (levitate.override { + config = { + imports = [ + "${modulesPath}/network" + "${modulesPath}/ssh" + "${modulesPath}/hardware.nix" + "${modulesPath}/kernel" + "${modulesPath}/outputs/tftpboot.nix" + "${modulesPath}/outputs.nix" + ]; + nixpkgs.buildPlatform = builtins.currentSystem; + services = { + # In this situation, we fallback to the appro VLAN. + # TODO: add support for the admin VLAN. + # Simplest DHCPv4 we can find. + dhcpv4 = svc.network.dhcp.client.build { + interface = config.hardware.networkInterfaces.lan; + }; + inherit (config.services) sshd; + defaultroute4 = svc.network.route.build { + via = "$(output ${config.services.dhcpv4} router)"; + target = "default"; + dependencies = [ config.services.dhcpv4 ]; + }; + }; + + defaultProfile.packages = [ mtdutils ]; + # Only keep root, which should inherit from DGN access control's root permissions. + users.root = config.users.root; + }; + }) + ]; +} diff --git a/machines/ap01/system.nix b/machines/ap01/system.nix new file mode 100644 index 0000000..a484311 --- /dev/null +++ b/machines/ap01/system.nix @@ -0,0 +1,28 @@ +{ pkgs, config, ... }: +let + svc = config.system.service; +in +{ + # Get moar random please + services = { + jitter = svc.jitter-rng.build { }; + packet_forwarding = svc.network.forward.build { }; + ntp = config.system.service.ntp.build { + pools = { + "pool.ntp.org" = [ "iburst" ]; + }; + + dependencies = [ config.services.jitter ]; + }; + }; + + boot.tftp = { + serverip = "192.0.2.10"; + ipaddr = "192.0.2.12"; + }; + + defaultProfile.packages = with pkgs; [ + zyxel-bootconfig + min-collect-garbage + ]; +} diff --git a/machines/ap01/wlan.nix b/machines/ap01/wlan.nix new file mode 100644 index 0000000..0644d69 --- /dev/null +++ b/machines/ap01/wlan.nix @@ -0,0 +1,93 @@ +{ config, pkgs, ... }: +let + svc = config.system.service; + secrets-1 = { + ssid = "DGNum 2G (N)"; + }; + secrets-2 = { + ssid = "DGNum 5G (AX)"; + }; + baseParams = { + country_code = "FR"; + hw_mode = "g"; + channel = 6; + wmm_enabled = 1; + ieee80211n = 1; + ht_capab = "[LDPC][GF][HT40-][HT40+][SHORT-GI-40][MAX-AMSDU-7935][TX-STBC]"; + auth_algs = 1; + wpa = 2; + wpa_pairwise = "TKIP CCMP"; + rsn_pairwise = "CCMP"; + }; + + radiusKeyMgmt = { + wpa_key_mgmt = "WPA-EAP"; + }; + + modernParams = { + hw_mode = "a"; + he_su_beamformer = 1; + he_su_beamformee = 1; + he_mu_beamformer = 1; + preamble = 1; + # Allow radar detection. + ieee80211d = 1; + ieee80211h = 1; + ieee80211ac = 1; + ieee80211ax = 1; + vht_capab = "[MAX-MPDU-7991][SU-BEAMFORMEE][SU-BEAMFORMER][RXLDPC][SHORT-GI-80][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN][TX-STBC-2BY1][RX-STBC-1][MU-BEAMFORMER]"; + vht_oper_chwidth = 1; + he_oper_chwidth = 1; + channel = 36; + vht_oper_centr_freq_seg0_idx = 42; + he_oper_centr_freq_seg0_idx = 42; + require_vht = 1; + }; + + clientRadius = { + ieee8021x = 1; + eapol_version = 2; + use_pae_group_addr = 1; + dynamic_vlan = 0; + vlan_tagged_interface = "lan"; + }; + + externalRadius = { + # TODO: when we have proper IPAM, set the right value here. + own_ip_addr = "127.0.0.1"; + nas_identifier = "ap01.dgnum.eu"; + + # No DNS here, hostapd do not support this mode. + auth_server_addr = "129.199.195.129"; + auth_server_port = 1812; + auth_server_shared_secret = "read it online"; + }; + + mkWifiSta = + params: interface: secrets: + svc.hostapd.build { + inherit interface; + package = pkgs.hostapd-radius; + params = params // secrets; + dependencies = [ config.services.jitter ]; + }; +in +{ + services = { + # wlan0 is the 2.4GHz interface. + hostap-1 = mkWifiSta ( + baseParams // radiusKeyMgmt + ) config.hardware.networkInterfaces.wlan0 secrets-1; + hostap-1-ready = svc.hostapd-ready.build { + interface = config.hardware.networkInterfaces.wlan0; + }; + # wlan1 is the 5GHz interface, e.g. AX capable. + hostap-2 = mkWifiSta ( + baseParams // clientRadius // externalRadius // radiusKeyMgmt // modernParams + ) config.hardware.networkInterfaces.wlan1 secrets-2; + # Oneshot that waits until the hostapd has set the interface in operational state. + hostap-2-ready = svc.hostapd-ready.build { + interface = config.hardware.networkInterfaces.wlan1; + }; + }; +} diff --git a/meta/nodes.nix b/meta/nodes.nix index c43f209..91dd6c0 100644 --- a/meta/nodes.nix +++ b/meta/nodes.nix @@ -177,4 +177,14 @@ system = "nixos"; }; }; + + ap01 = { + site = "unknown"; + adminGroups = [ "fai" ]; + + nixpkgs = { + system = "zyxel-nwa50ax"; + version = "unstable"; + }; + }; }