272 lines
10 KiB
Nix
272 lines
10 KiB
Nix
# Physical safety, shared host policy and built files; no activation/connections.
|
|
{
|
|
config,
|
|
ec2Config,
|
|
pkgs,
|
|
}:
|
|
let
|
|
inherit (pkgs) lib;
|
|
dev = config.users.users.dev;
|
|
btrfsDevice = "/dev/disk/by-uuid/8a16015f-d6f8-4f74-8558-6261b9112216";
|
|
mounts = {
|
|
"/" = {
|
|
device = btrfsDevice;
|
|
fsType = "btrfs";
|
|
};
|
|
"/home" = {
|
|
device = btrfsDevice;
|
|
fsType = "btrfs";
|
|
options = [ "subvol=home" ];
|
|
};
|
|
"/nix" = {
|
|
device = btrfsDevice;
|
|
fsType = "btrfs";
|
|
options = [ "subvol=nix" ];
|
|
};
|
|
"/boot" = {
|
|
device = "/dev/disk/by-uuid/DEC5-51CB";
|
|
fsType = "vfat";
|
|
options = [
|
|
"fmask=0077"
|
|
"dmask=0077"
|
|
];
|
|
};
|
|
};
|
|
tests = [
|
|
{
|
|
assertion = config.boot.loader.systemd-boot.enable && !config.boot.loader.grub.enable;
|
|
message = "The physical host must use systemd-boot, never EC2's GRUB disk.";
|
|
}
|
|
{
|
|
assertion = lib.all (
|
|
mount:
|
|
let
|
|
actual = config.fileSystems.${mount};
|
|
expected = mounts.${mount};
|
|
in
|
|
actual.device == expected.device
|
|
&& actual.fsType == expected.fsType
|
|
&& lib.all (option: builtins.elem option actual.options) (expected.options or [ ])
|
|
) (builtins.attrNames mounts);
|
|
message = "Preserve the installed root/home/nix/EFI filesystems and Btrfs subvolumes.";
|
|
}
|
|
{
|
|
assertion =
|
|
config.users.mutableUsers
|
|
&& !(config.users.users ? kbot)
|
|
&& dev.password == null
|
|
&& dev.hashedPassword == null
|
|
&& dev.hashedPasswordFile == null;
|
|
message = "Retire kbot without overwriting dev's locally established password.";
|
|
}
|
|
{
|
|
assertion =
|
|
dev.isNormalUser
|
|
&& dev.uid == 1001
|
|
&& dev.home == "/home/dev"
|
|
&& builtins.elem "wheel" dev.extraGroups
|
|
&& builtins.elem "networkmanager" dev.extraGroups
|
|
&& builtins.attrNames config.home-manager.users == [ "dev" ];
|
|
message = "dev is the sole managed daily account, with local administration/network access.";
|
|
}
|
|
{
|
|
assertion =
|
|
config.services.displayManager.sddm.enable
|
|
&& !config.services.desktopManager.plasma6.enable
|
|
&& config.programs.hyprland.enable
|
|
&& config.programs.hyprland.withUWSM
|
|
&& !config.services.greetd.enable
|
|
&& !config.services.displayManager.autoLogin.enable
|
|
&& config.services.displayManager.defaultSession == "hyprland-uwsm"
|
|
&& !(builtins.elem "hyprland" config.services.displayManager.sessionData.sessionNames);
|
|
message = "Offer only the managed Hyprland desktop in SDDM, without Plasma or autologin.";
|
|
}
|
|
{
|
|
assertion =
|
|
config.networking.networkmanager.enable
|
|
&& config.networking.networkmanager.dns == "systemd-resolved"
|
|
&& builtins.elem (lib.getName pkgs.networkmanager-openvpn) (
|
|
map lib.getName config.networking.networkmanager.plugins
|
|
);
|
|
message = "Keep NetworkManager with resolved DNS and its OpenVPN integration.";
|
|
}
|
|
{
|
|
assertion =
|
|
lib.all
|
|
(
|
|
c:
|
|
let
|
|
unit = c.systemd.services.systemd-resolved;
|
|
in
|
|
c.services.resolved.enable
|
|
&& builtins.elem "sysinit.target" unit.wantedBy
|
|
&& builtins.elem "dbus-org.freedesktop.resolve1.service" unit.aliases
|
|
&& unit.serviceConfig.Restart == "always"
|
|
&& unit.serviceConfig.RestartSec == "5s"
|
|
&& unit.unitConfig.StartLimitIntervalSec == 0
|
|
&& !c.networking.resolvconf.enable
|
|
&& c.networking.resolvconf.package == c.systemd.package
|
|
&& c.environment.etc."resolv.conf".source == "/run/systemd/resolve/stub-resolv.conf"
|
|
&& c.services.resolved.settings.Resolve.DNS == [ ]
|
|
&& !c.services.resolved.settings.Resolve.LLMNR
|
|
&& !c.services.resolved.settings.Resolve.MulticastDNS
|
|
)
|
|
[
|
|
config
|
|
ec2Config
|
|
];
|
|
message = "Both hosts need boot-enabled, restarting resolved with D-Bus/stub DNS integration, without hard-coded DNS servers.";
|
|
}
|
|
{
|
|
assertion =
|
|
config.systemd.services.nixos-update.environment.NIXOS_UPDATE_HOST == "nixos"
|
|
&& config.systemd.services.nixos-update.environment.NIXOS_CONFIG_REPO == "/etc/nix"
|
|
&& config.systemd.services.nixos-update.serviceConfig.User == "dev"
|
|
&& builtins.elem "Z /etc/nix - dev users -" config.systemd.tmpfiles.rules
|
|
&& config.systemd.timers.nixos-update.timerConfig.Persistent
|
|
&& !(config.systemd.services ? amazon-ssm-agent)
|
|
&& !(builtins.elem "Z /etc/nixos - dev users -" config.systemd.tmpfiles.rules);
|
|
message = "The laptop must keep its own checkout/target, not EC2's deployment settings.";
|
|
}
|
|
{
|
|
assertion =
|
|
lib.all
|
|
(
|
|
c:
|
|
let
|
|
update = c.systemd.services.nixos-update;
|
|
in
|
|
update.serviceConfig.User == "dev"
|
|
&& update.serviceConfig.Group == "users"
|
|
&& update.serviceConfig.ExecStart == config.systemd.services.nixos-update.serviceConfig.ExecStart
|
|
&& c.systemd.timers.nixos-update.timerConfig == config.systemd.timers.nixos-update.timerConfig
|
|
&& !(update.environment ? NIXOS_UPDATE_MODE)
|
|
&& builtins.elem "Z ${update.environment.NIXOS_CONFIG_REPO} - dev users -" c.systemd.tmpfiles.rules
|
|
&& builtins.elem "Z /var/cache/nixos-update - dev users -" c.systemd.tmpfiles.rules
|
|
&& c.users.mutableUsers
|
|
&& c.time.timeZone == "America/Chicago"
|
|
&& c.i18n.defaultLocale == "en_US.UTF-8"
|
|
)
|
|
[
|
|
config
|
|
ec2Config
|
|
];
|
|
message = "Both hosts must share the dev-owned checkout/updater and user preferences; no per-host activation policy.";
|
|
}
|
|
{
|
|
assertion =
|
|
ec2Config.systemd.services.nixos-update.environment.NIXOS_UPDATE_HOST == "dev"
|
|
&& ec2Config.systemd.services.nixos-update.environment.NIXOS_CONFIG_REPO == "/etc/nixos"
|
|
&& ec2Config.boot.loader.grub.enable
|
|
&& ec2Config.boot.loader.grub.device == "/dev/xvda"
|
|
&& !ec2Config.boot.loader.systemd-boot.enable
|
|
&& ec2Config.fileSystems."/".device == "/dev/disk/by-label/nixos"
|
|
&& ec2Config.fileSystems."/".fsType == "ext4"
|
|
&& ec2Config.services.openssh.enable
|
|
&& ec2Config.services.amazon-ssm-agent.enable
|
|
&& ec2Config.networking.dhcpcd.enable
|
|
&& ec2Config.services.resolved.enable
|
|
&& !ec2Config.networking.networkmanager.enable
|
|
&& !ec2Config.services.displayManager.sddm.enable;
|
|
message = "EC2 must retain its AWS boot, disks, networking and remote recovery, not laptop hardware services.";
|
|
}
|
|
{
|
|
assertion =
|
|
lib.all
|
|
(package: builtins.elem (lib.getName package) (map lib.getName config.environment.systemPackages))
|
|
(
|
|
with pkgs;
|
|
[
|
|
wireguard-tools
|
|
openvpn
|
|
iperf3
|
|
nmap
|
|
traceroute
|
|
whois
|
|
dnsutils
|
|
tcpdump
|
|
ethtool
|
|
netcat-openbsd
|
|
socat
|
|
fping
|
|
ldns
|
|
torsocks
|
|
proxychains-ng
|
|
tor
|
|
]
|
|
)
|
|
&& config.programs.mtr.enable;
|
|
message = "The VPN/proxy clients and network diagnostics must remain installed.";
|
|
}
|
|
{
|
|
assertion =
|
|
lib.all
|
|
(
|
|
c:
|
|
let
|
|
tor = c.services.tor;
|
|
unit = c.systemd.services.tor;
|
|
in
|
|
tor.enable
|
|
&& tor.client.enable
|
|
&& !tor.relay.enable
|
|
&& !tor.openFirewall
|
|
&& tor.settings.ClientOnly
|
|
&& tor.settings.ORPort == [ ]
|
|
&& tor.settings.DirPort == [ ]
|
|
&& tor.settings.ExitPolicy == [ "reject *:*" ]
|
|
&& tor.relay.onionServices == { }
|
|
&& !tor.controlSocket.enable
|
|
&& tor.settings.ControlPort == [ ]
|
|
&& !tor.client.dns.enable
|
|
&& !tor.client.transparentProxy.enable
|
|
&& tor.settings.DNSPort == [ ]
|
|
&& tor.settings.TransPort == [ ]
|
|
&& builtins.length tor.settings.SOCKSPort == 1
|
|
&& lib.all (
|
|
listener:
|
|
listener.addr == "127.0.0.1"
|
|
&& listener.port == 9050
|
|
&& listener.IsolateDestAddr
|
|
&& listener.IsolateSOCKSAuth
|
|
) tor.settings.SOCKSPort
|
|
&& builtins.elem "multi-user.target" unit.wantedBy
|
|
&& unit.serviceConfig.Restart == "always"
|
|
&& unit.serviceConfig.RestartSec == "5s"
|
|
&& unit.unitConfig.StartLimitIntervalSec == 0
|
|
&& unit.serviceConfig.User == "tor"
|
|
&& unit.serviceConfig.NoNewPrivileges
|
|
)
|
|
[
|
|
config
|
|
ec2Config
|
|
];
|
|
message = "Both hosts need a boot-enabled, restarting, loopback-only Tor client, without relay, control, DNS or transparent-proxy listeners.";
|
|
}
|
|
{
|
|
assertion =
|
|
config.networking.wireguard.interfaces == { }
|
|
&& config.networking.wg-quick.interfaces == { }
|
|
&& config.services.openvpn.servers == { };
|
|
message = "Installing VPN tools must not invent tunnels, peers or credentials.";
|
|
}
|
|
];
|
|
in
|
|
assert lib.all (test: lib.assertMsg test.assertion test.message) tests;
|
|
pkgs.runCommand "physical-config-check" { } ''
|
|
sessions=${config.services.displayManager.sessionData.desktops}/share
|
|
test ! -e "$sessions/wayland-sessions/plasma.desktop"
|
|
test -f "$sessions/wayland-sessions/hyprland-uwsm.desktop"
|
|
test ! -e "$sessions/wayland-sessions/hyprland.desktop"
|
|
for tool in wg wg-quick openvpn iperf3 nmap traceroute whois mtr dig tcpdump ethtool nc socat \
|
|
fping drill torsocks proxychains4 tor nm-connection-editor resolvectl; do
|
|
test -x "${config.system.path}/bin/$tool"
|
|
done
|
|
# Validate the exact generated torrc offline without touching live Tor state.
|
|
mkdir -m 700 "$TMPDIR/tor"
|
|
${config.services.tor.package}/bin/tor --verify-config \
|
|
-f ${builtins.head config.systemd.services.tor.restartTriggers} \
|
|
--DataDirectory "$TMPDIR/tor"
|
|
touch "$out"
|
|
''
|