diff --git a/modules/common/security/apparmor/default.nix b/modules/common/security/apparmor/default.nix new file mode 100755 index 000000000..eac8ee7ff --- /dev/null +++ b/modules/common/security/apparmor/default.nix @@ -0,0 +1,30 @@ +# Copyright 2024-2025 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + ... +}: let + cfg = config.ghaf.security.apparmor; +in { + ## Option to enable Apparmor security + options.ghaf.security.apparmor = { + enable = lib.mkOption { + description = '' + Enable Apparmor security. + ''; + type = lib.types.bool; + default = false; + }; + }; + + imports = [ + ./profiles/firefox.nix + ./profiles/chromium.nix + ]; + + config = lib.mkIf cfg.enable { + security.apparmor.enable = true; + security.apparmor.killUnconfinedConfinables = lib.mkDefault true; + }; +} diff --git a/modules/common/security/apparmor/profiles/chromium.nix b/modules/common/security/apparmor/profiles/chromium.nix new file mode 100644 index 000000000..69cf723d6 --- /dev/null +++ b/modules/common/security/apparmor/profiles/chromium.nix @@ -0,0 +1,186 @@ +# Copyright 2024-2025 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.ghaf.security.apparmor; + xprofile = + if config.ghaf.security.system-security.enable + then '' + capability sys_admin, + capability sys_chroot, + + capability chown, + capability fsetid, + capability setgid, + capability setuid, + capability dac_override, + capability sys_chroot, + + capability sys_ptrace, + ptrace (read, readby), + capability sys_chroot, + capability ipc_lock, + + capability setuid, + capability setgid, + + owner @{PROC}/[0-9]*/gid_map w, + owner @{PROC}/[0-9]*/setgroups w, + owner @{PROC}/[0-9]*/uid_map w, + } + '' + else '' + } + ''; +in { + ## Option to enable Apparmor profile for chromium + options.ghaf.security.apparmor.apps.chromium = { + enable = lib.mkOption { + description = '' + Enable Chromium AppArmor profile. + ''; + type = lib.types.bool; + default = false; + }; + }; + ## Apparmor profile for Chromium + config.security.apparmor.policies."bin.chromium" = lib.mkIf cfg.apps.chromium.enable { + profile = + '' + abi , + include + + @{CHROMIUM} = ${pkgs.chromium.browser}/libexec/chromium/chromium + @{INTEGER}=[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],} + @{ETC}=/etc + @{NIX_STORE}=/nix/store + + profile chromium @{CHROMIUM} flags=(enforce){ + include + include + include + include + include + include + include + include + + include "${pkgs.apparmorRulesFromClosure {name = "chromium";} [pkgs.chromium]}" + + ${config.environment.etc."os-release".source} r, + ${config.environment.etc."lsb-release".source} r, + + # All of these are for sanely dropping from root and chrooting + + # optional + capability sys_resource, + owner @{PROC}/[0-9]*/gid_map w, + owner @{PROC}/[0-9]*/setgroups w, + owner @{PROC}/[0-9]*/uid_map w, + + @{ETC}/nixos/** r, + @{ETC}/nix/** r, + @{NIX_STORE}/** mrix, + + @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size r, + @{sys}/devices/system/cpu/present r, + @{sys}/devices/system/cpu/kernel_max r, + @{sys}/devices/system/cpu/cpu[0-9]/cache/index[0-9]/size r, + @{sys}/bus/ r, + @{sys}/bus/** r, + + @{sys}/class/ r, + @{sys}/class/** r, + @{sys}/devices/pci*/** rw, + @{sys}/devices/virtual/tty/** r, + @{sys}/devices/virtual/dmi/** r, + + /tmp/.X[0-9]*-lock r, + + @{CHROMIUM} mrix, + ${pkgs.chromium}/share/{,**} r, + ${pkgs.chromium.sandbox}/bin/* rix, + ${pkgs.chromium.browser} r, + ${pkgs.chromium.browser}/share/{,**} r, + ${pkgs.chromium.browser}/libexec/chromium/chromium rix, + ${pkgs.chromium.browser}/libexec/chromium/*.so mr, + ${pkgs.chromium.browser}/libexec/chromium/* rix, + ${pkgs.chromium.browser}/libexec/chromium/** r, + + @{PROC} r, + @{PROC}/[0-9]*/net/ipv6_route r, + @{PROC}/[0-9]*/net/arp r, + @{PROC}/[0-9]*/net/if_inet6 r, + @{PROC}/[0-9]*/net/route r, + @{PROC}/[0-9]*/net/ipv6_route r, + @{PROC}/[0-9]*/stat rix, + @{PROC}/[0-9]*/task/@{tid}/comm rw, + @{PROC}/[0-9]*/task/@{tid}/status rix, + owner @{PROC}/[0-9]*/cgroup r, + owner @{PROC}/[0-9]*/fd/ r, + owner @{PROC}/[0-9]*/io r, + owner @{PROC}/[0-9]*/mountinfo r, + owner @{PROC}/[0-9]*/mounts r, + owner @{PROC}/[0-9]*/oom_score_adj w, + owner @{PROC}/[0-9]*/smaps rix, + owner @{PROC}/[0-9]*/statm rix, + owner @{PROC}/[0-9]*/task/ r, + owner @{PROC}/[0-9]*/cmdline rix, + owner @{PROC}/[0-9]*/environ rix, + owner @{PROC}/[0-9]*/clear_refs rw, + owner @{PROC}/self/* r, + owner @{PROC}/self/fd/* rw, + @{PROC}/sys/kernel/yama/ptrace_scope rw, + @{PROC}/sys/fs/inotify/max_user_watches r, + @{PROC}/ati/major r, + + /dev/fb0 rw, + /dev/ r, + /dev/hidraw@{INTEGER} rw, + /dev/shm/** rw, + /dev/tty rw, + /dev/video@{INTEGER} rw, + owner /dev/shm/pulse-shm* m, + owner /dev/tty@{INTEGER} rw, + + owner @{HOME} r, + owner @{HOME}/.cache/chromium wrk, + owner @{HOME}/.cache/mesa_shader_cache/index wrk, + owner @{HOME}/.cache/chromium/** mrwk, + owner @{HOME}/.cache/fontconfig/** rwk, + owner @{HOME}/.config/chromium rwkm, + owner @{HOME}/.config/chromium/** rwkm, + owner @{HOME}/.config/** rw, + owner @{HOME}/.local/share/mime/mime.cache m, + owner @{HOME}/.pki/nssdb/ rwk, + owner @{HOME}/.pki/nssdb/** rwk, + owner @{HOME}/Downloads/ r, + owner @{HOME}/Downloads/* rw, + owner @{run}/user/1000/ rw, + owner @{run}/user/1000/** rw, + owner /tmp/** rwk, + owner /var/tmp/** m, + + owner /tmp/chromiumargs.?????? rw, + + deny /boot/EFI/systemd/** r, + deny /boot/EFI/nixos/** r, + deny /boot/loader/** r, + deny /.suspended r, + deny /boot/vmlinuz* r, + deny /var/cache/fontconfig/ w, + + ### Networking ### + network inet stream, + network inet6 stream, + network inet dgram, + #network inet6 dgrap, + network netlink raw, + '' + + xprofile; + }; +} diff --git a/modules/common/security/apparmor/profiles/firefox.nix b/modules/common/security/apparmor/profiles/firefox.nix new file mode 100755 index 000000000..086bf5016 --- /dev/null +++ b/modules/common/security/apparmor/profiles/firefox.nix @@ -0,0 +1,171 @@ +# Copyright 2024-2025 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.ghaf.security.apparmor; + xprofile = + if config.ghaf.security.system-security.enable + then '' + capability sys_admin, + capability sys_chroot, + owner @{PROC}/@{pid}/gid_map w, + owner @{PROC}/@{pid}/setgroups w, + owner @{PROC}/@{pid}/uid_map w, + } + '' + else '' + } + ''; +in { + ## Option to enable Apparmor profile for Firefox + options.ghaf.security.apparmor.apps.firefox = { + enable = lib.mkOption { + description = '' + Enable firefox AppArmor profile. + ''; + type = lib.types.bool; + default = false; + }; + }; + + ## Apparmor profile for Firefox + config.security.apparmor.policies."bin.firefox" = lib.mkIf cfg.apps.firefox.enable { + profile = + '' + abi , + include + + @{MOZ_LIBDIR} = ${pkgs.firefox}/lib/firefox{,-esr} + @{MOZ_HOMEDIR} = @{HOME}/.mozilla + @{CACHEDIR} = @{HOME}/.cache + @{MOZ_CACHEDIR} = @{CACHEDIR}/mozilla + @{FIREFOX} = ${pkgs.firefox}/bin/firefox + @{INTEGER}=[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],} + @{ETC}=/etc + @{NIX_STORE}=/nix/store + + profile firefox @{FIREFOX} flags=(enforce){ + include + include + include + include + include + include + include + include + + include "${pkgs.apparmorRulesFromClosure {name = "firefox";} [pkgs.firefox]}" + + # Uncomment these, if kernel.unprivileged_userns_clone = 1 + #capability sys_admin, + #capability sys_chroot, + #owner @{PROC}/@{pid}/gid_map w, + #owner @{PROC}/@{pid}/setgroups w, + #owner @{PROC}/@{pid}/uid_map w, + + ${config.environment.etc."os-release".source} r, + ${config.environment.etc."lsb-release".source} r, + + @{ETC}/nixos/** r, + @{ETC}/nix/** r, + @{NIX_STORE}/** mr, + + @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size r, + @{sys}/devices/system/cpu/present r, + @{sys}/devices/system/cpu/cpu[0-9]/cache/index[0-9]/size r, + @{sys}/bus/pci rw, + @{sys}/bus/pci_express rw, + @{sys}/bus/pci/devices/ rw, + @{sys}/bus/pci/devices/** rw, + @{sys}/devices/pci*/** rw, + /tmp/.X[0-9]*-lock r, + + @{FIREFOX} mrix, + ${pkgs.firefox}/lib/firefox/firefox rix, + ${pkgs.firefox}/lib/firefox/glxtest rix, + ${pkgs.firefox}/lib/firefox/firefox-bin rix, + ${pkgs.firefox}/lib/firefox/*.so mr, + ${pkgs.firefox}/share/firefox/{,**} r, + ${pkgs.firefox}/share/firefox/fonts r, + ${pkgs.firefox}/lib/mozilla/plugins/ r, + ${pkgs.firefox}/lib/mozilla/plugins/libvlcplugin.so mr, + + ${pkgs.firefox-unwrapped}/lib/firefox/glxtest rix, + ${pkgs.firefox-unwrapped}/lib/firefox/firefox rix, + ${pkgs.firefox-unwrapped}/lib/firefox/firefox-bin rix, + ${pkgs.firefox-unwrapped}/lib/firefox/*.so mr, + ${pkgs.firefox-unwrapped}/lib/firefox/fonts r, + ${pkgs.firefox-unwrapped}/lib/firefox/pingsender r, + ${pkgs.firefox-unwrapped}/share/firefox/{,**} r, + ${pkgs.firefox-unwrapped}/lib/mozilla/plugins/ r, + ${pkgs.firefox-unwrapped}/lib/mozilla/plugins/libvlcplugin.so mr, + + @{PROC}/@{pid}/net/ipv6_route r, + @{PROC}/@{pid}/net/arp r, + @{PROC}/@{pid}/net/if_inet6 r, + @{PROC}/@{pid}/net/route r, + @{PROC}/@{pid}/net/ipv6_route r, + owner @{PROC}/@{pid}/cgroup r, + owner @{PROC}/@{pid}/fd/ r, + owner @{PROC}/@{pid}/mountinfo r, + owner @{PROC}/@{pid}/mounts r, + owner @{PROC}/@{pid}/oom_score_adj w, + owner @{PROC}/@{pid}/smaps r, + owner @{PROC}/@{pid}/stat r, + owner @{PROC}/@{pid}/statm r, + owner @{PROC}/@{pid}/task/ r, + owner @{PROC}/@{pid}/task/@{tid}/comm rw, + owner @{PROC}/@{pid}/task/@{tid}/stat r, + owner @{PROC}/@{pids}/cmdline r, + owner @{PROC}/@{pids}/environ r, + owner @{PROC}/self/* r, + owner @{PROC}/self/fd/* rw, + + /dev/fb0 rw, + /dev/ r, + /dev/hidraw@{INTEGER} rw, + /dev/shm/ r, + /dev/tty rw, + /dev/video@{INTEGER} rw, + owner /dev/shm/org.chromium.* rw, + owner /dev/shm/org.mozilla.ipc.@{pid}.@{INTEGER} rw, + owner /dev/shm/wayland.mozilla.ipc.@{INTEGER} rw, + owner /dev/tty@{INTEGER} rw, + + owner @{MOZ_HOMEDIR}/ rw, + owner @{MOZ_HOMEDIR}/{extensions,systemextensionsdev}/ rw, + owner @{MOZ_HOMEDIR}/firefox/ rw, + owner @{MOZ_HOMEDIR}/firefox/installs.ini rw, + owner @{MOZ_HOMEDIR}/firefox/profiles.ini rw, + owner @{MOZ_HOMEDIR}/firefox/*/ rw, + owner @{MOZ_HOMEDIR}/firefox/*/** rwk, + owner @{HOME}/.cache/ rw, + owner @{MOZ_CACHEDIR}/ rw, + owner @{MOZ_CACHEDIR}/** rwk, + owner @{CACHEDIR}/mesa_shader_cache/index wr, + owner @{run}/user/1000/ rw, + owner @{run}/user/1000/** rw, + owner /tmp/** m, + owner /var/tmp/** m, + + deny /boot/EFI/systemd/** r, + deny /boot/EFI/nixos/** r, + deny /boot/loader/** r, + deny /.suspended r, + deny /boot/vmlinuz* r, + deny /var/cache/fontconfig/ w, + + ### Networking ### + network inet stream, + network inet6 stream, + network inet dgram, + #network inet6 dgrap, + network netlink raw, + '' + + xprofile; + }; +} diff --git a/modules/common/security/clamav/default.nix b/modules/common/security/clamav/default.nix new file mode 100755 index 000000000..505925094 --- /dev/null +++ b/modules/common/security/clamav/default.nix @@ -0,0 +1,86 @@ +# Copyright 2024-2025 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + ... +}: let + cfg = config.ghaf.security.clamav; +in { + ## Antivirus in Ghaf + options.ghaf.security.clamav = { + # Option to enable + enable = lib.mkOption { + description = '' + Enable Clamav antivirus. + ''; + type = lib.types.bool; + default = false; + }; + + # Option to enable live update of virus database + live-update = lib.mkOption { + description = '' + Enable live update. + ''; + type = lib.types.bool; + default = false; + }; + }; + + config.services.clamav = lib.mkIf cfg.enable { + # Enable Clamav antivirus daemon + daemon = { + enable = true; + settings = { + #TODO: write more configuration for Clamav + #https://linux.die.net/man/5/clamd.conf + + # Uncomment these options to enable logging. + # LogFile must be writable for the user running daemon. + # A full path is required. + + #LogFile = "/tmp/clamd.log"; + #LogFileMaxSize = "2M"; + #LogTime = "yes"; + #LogRotate = "yes"; + #ExtendedDetectionInfo = "yes"; + + # Always block cloaked URLs, even if URL isn't in database. + # This can lead to false positives. + PhishingAlwaysBlockCloak = "no"; + + # Allow heuristic match to take precedence. + # When enabled, if a heuristic scan (such as phishingScan) detects + # a possible virus/phish it will stop scan immediately. Recommended, saves CPU + # scan-time. + # When disabled, virus/phish detected by heuristic scans will be reported only at + # the end of a scan. If an archive contains both a heuristically detected + # virus/phish, and a real malware, the real malware will be reported + HeuristicScanPrecedence = "yes"; + + # Enable the Data Loss Prevention module + StructuredDataDetection = "yes"; + + # Stop daemon when libclamav reports out of memory condition. + ExitOnOOM = "yes"; + + # With this option clamav will try to detect broken executables (both PE and + # ELF) and mark them as Broken.Executable. + DetectBrokenExecutables = "yes"; + }; + }; + updater = lib.mkIf cfg.live-update { + # Enable live update of virus database + enable = true; + settings = { + #TODO: write updater configuration + #https://linux.die.net/man/5/freshclam.conf + }; + #Update virus database every hour + interval = "hourly"; + #Update from 12 databases daily + frequency = 12; + }; + }; +} diff --git a/modules/common/security/default.nix b/modules/common/security/default.nix index f91f3ff50..9dd02a6a8 100644 --- a/modules/common/security/default.nix +++ b/modules/common/security/default.nix @@ -3,5 +3,11 @@ { imports = [ ./sshkeys.nix + ./apparmor + ./clamav + ./fail2ban + ./firejail + ./networking.nix + ./system.nix ]; } diff --git a/modules/common/security/fail2ban/default.nix b/modules/common/security/fail2ban/default.nix new file mode 100755 index 000000000..4568fa563 --- /dev/null +++ b/modules/common/security/fail2ban/default.nix @@ -0,0 +1,36 @@ +# Copyright 2024-2025 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + ... +}: let + cfg = config.ghaf.security.fail2ban; +in { + #imports = [../../desktop]; + ## Option to enable fail2ban sandboxing + options.ghaf.security.fail2ban = { + enable = lib.mkOption { + description = '' + Enable fail2ban. + ''; + type = lib.types.bool; + default = false; + }; + }; + + ## Enable fail2ban sandboxing + config = { + services.fail2ban = lib.mkIf cfg.enable { + enable = true; + bantime = "30m"; + maxretry = 3; + bantime-increment.enable = true; + bantime-increment.factor = "2"; + jails = { + # TODO: define jails here + # sshd is jailed by default + }; + }; + }; +} diff --git a/modules/common/security/firejail/default.nix b/modules/common/security/firejail/default.nix new file mode 100755 index 000000000..af695577c --- /dev/null +++ b/modules/common/security/firejail/default.nix @@ -0,0 +1,89 @@ +# Copyright 2024-2025 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.ghaf.security.firejail; +in { + imports = [../../../desktop]; + ## Option to enable Firejail sandboxing + options.ghaf.security.firejail = { + enable = lib.mkOption { + description = '' + Enable Firejail sandboxing. + ''; + type = lib.types.bool; + default = false; + }; + + apps = { + firefox.enable = lib.mkOption { + description = '' + Enable sandboxing for Firefox. Enable this option when the browser is enabled in host. + ''; + type = lib.types.bool; + default = false; + }; + + chromium.enable = lib.mkOption { + description = '' + Enable sandboxing for Chromium. Enable this option when the browser is enabled in host. + ''; + type = lib.types.bool; + default = false; + }; + }; + }; + + ## Enable Firejail sandboxing + config = lib.mkIf cfg.enable { + ghaf.graphics = lib.mkIf config.ghaf.profiles.graphics.enable { + demo-apps = lib.mkMerge [ + (lib.mkIf cfg.apps.firefox.enable { + firefox = lib.mkForce false; + }) + (lib.mkIf cfg.apps.chromium.enable { + chromium = lib.mkForce false; + }) + ]; + + launchers = + lib.optional cfg.apps.firefox.enable { + name = "Firefox-safe"; + path = "/run/current-system/sw/bin/firefox"; + icon = "${pkgs.icon-pack}/firefox.svg"; + } + ++ lib.optional cfg.apps.chromium.enable { + name = "Chromium-safe"; + path = "/run/current-system/sw/bin/chromium"; + icon = "${pkgs.icon-pack}/chromium.svg"; + }; + }; + + programs.firejail = { + enable = true; + wrappedBinaries = { + # Firefox profile + firefox = lib.mkIf cfg.apps.firefox.enable { + executable = "${lib.getBin pkgs.firefox}/bin/firefox"; + profile = "${pkgs.firejail}/etc/firejail/firefox.profile"; + extraArgs = [ + "--whitelist=/run/current-system/sw/bin/firefox" + ]; + }; + + # Chromium profile + chromium = lib.mkIf cfg.apps.chromium.enable { + executable = "${lib.getBin pkgs.chromium}/bin/chromium --enable-features=UseOzonePlatform --ozone-platform=wayland"; + profile = "${pkgs.firejail}/etc/firejail/chromium-browser.profile"; + extraArgs = [ + "--whitelist=/run/current-system/sw/bin/chromium" + ]; + }; + }; + }; + }; +} diff --git a/modules/common/security/networking.nix b/modules/common/security/networking.nix new file mode 100755 index 000000000..7191cbcb7 --- /dev/null +++ b/modules/common/security/networking.nix @@ -0,0 +1,85 @@ +# Copyright 2024-2025 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + ... +}: let + cfg = config.ghaf.security.network; +in { + ## Option to enable IP security + options.ghaf.security.network.ipsecurity.enable = lib.mkOption { + description = '' + Enable Internet Protocol security. + ''; + type = lib.types.bool; + default = true; + }; + + options.ghaf.security.network.bpf-access-level = lib.mkOption { + description = '' + Aceess level for bpf: + 0: disable bpf JIT + 1: priviledged access only + no restriction for any other value + ''; + type = lib.types.int; + default = 0; + }; + + config.boot.kernel.sysctl = lib.mkMerge [ + (lib.mkIf cfg.ipsecurity.enable { + # Disable IPv6 + "net.ipv6.conf.all.disable_ipv6" = lib.mkForce 1; + "net.ipv6.conf.default.disable_ipv6" = lib.mkForce 1; + "net.ipv6.conf.lo.disable_ipv6" = lib.mkForce 1; + + # Prevent SYN flooding + "net.ipv4.tcp_syncookies" = lib.mkForce 1; + "net.ipv4.tcp_syn_retries" = lib.mkForce 2; + "net.ipv4.tcp_synack_retries" = lib.mkForce 2; + "net.ipv4.tcp_max_syn_backlog" = lib.mkForce 4096; + + # Drop RST packets for sockets in the time-wait state + "net.ipv4.tcp_rfc1337" = lib.mkForce 1; + + # Enable RP Filter + "net.ipv4.conf.all.rp_filter" = lib.mkForce 1; + "net.ipv4.conf.default.rp_filter" = lib.mkForce 1; + + # Disable redirect acceptance + "net.ipv4.conf.all.accept_redirects" = lib.mkForce 0; + "net.ipv4.conf.default.accept_redirects" = lib.mkForce 0; + "net.ipv4.conf.all.secure_redirects" = lib.mkForce 0; + "net.ipv4.conf.default.secure_redirects" = lib.mkForce 0; + "net.ipv4.conf.all.send_redirects" = lib.mkForce 0; + "net.ipv4.conf.default.send_redirects" = lib.mkForce 0; + + # Ignore source-routed IP packets + "net.ipv4.conf.all.accept_source_route" = lib.mkForce 0; + "net.ipv4.conf.default.accept_source_route" = lib.mkForce 0; + + # Ignore ICMP echo requests + "net.ipv4.icmp_echo_ignore_all" = lib.mkForce 1; + + # Log Martian packets + "net.ipv4.conf.all.log_martians" = lib.mkDefault 0; + "net.ipv4.conf.default.log_martians" = lib.mkDefault 0; + + # Ignore bogus ICMP error responses + "net.ipv4.icmp_ignore_bogus_error_responses" = lib.mkForce 1; + }) + + (lib.mkIf (cfg.bpf-access-level == 0) { + # Disable BPF JIT compiler (to eliminate spray attacks) + "net.core.bpf_jit_enable" = lib.mkDefault false; + }) + + (lib.mkIf (cfg.bpf-access-level == 1) { + # Provide BPF access to privileged users + # TODO: test if it works with Tetragon/Suricata + "kernel.unprivileged_bpf_disabled" = lib.mkOverride 500 1; + "net.core.bpf_jit_harden" = lib.mkForce 2; + }) + ]; +} diff --git a/modules/common/security/system.nix b/modules/common/security/system.nix new file mode 100755 index 000000000..a06cb731b --- /dev/null +++ b/modules/common/security/system.nix @@ -0,0 +1,313 @@ +# Copyright 2024-2025 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.ghaf.security; +in { + options.ghaf.security = { + users = { + strong-password = { + enable = lib.mkOption { + description = '' + Enforce Strong password for each user. + ''; + type = lib.types.bool; + default = true; + }; + + min-passwd-len = lib.mkOption { + description = '' + Minimum password length. + ''; + type = lib.types.int; + default = 8; + }; + }; + + root.enable = lib.mkOption { + description = '' + Disable root login. + ''; + type = lib.types.bool; + default = true; + }; + + sudo = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether to enable the {command}`sudo` command, which + allows non-root users to execute commands as root. + ''; + }; + + extraConfig = lib.mkOption { + type = lib.types.lines; + default = ""; + description = '' + Extra configuration text appended to {file}`sudoers`. + ''; + }; + }; + }; + + system-security = { + enable = lib.mkOption { + description = '' + Enables basic linux security mechanism. + ''; + type = lib.types.bool; + default = false; + }; + lock-kernel-modules = lib.mkOption { + description = '' + Lock dynamic kernel modules. + ''; + type = lib.types.bool; + default = true; + }; + memory-allocator = lib.mkOption { + description = '' + Memory allocator. + Options: "libc", "graphene-hardened", "jemalloc", "mimalloc", "scudo" + ''; + type = lib.types.enum ["libc" "graphene-hardened" "jemalloc" "mimalloc" "scudo"]; + default = "libc"; + }; + misc = { + enable-all = lib.mkOption { + description = '' + Enable additional security features, which may affect performance. Specific options can be + activated individually to achieve a balance between performance and security. + ''; + type = lib.types.bool; + default = false; + }; + disableHyperthreading = lib.mkOption { + description = '' + Disable hyperthreading. Disabling hyperthreading means that only physical + CPU cores will be usable at runtime, potentially at significant performance cost. + ''; + type = lib.types.bool; + default = false; + }; + vm-flushL1DCache = lib.mkOption { + description = '' + Flush L1 data cache every time the hypervisor enters the guest. It provides security + May incur significant performance cost. + ''; + type = lib.types.bool; + default = false; + }; + isolatePageTables = lib.mkOption { + description = '' + Isolate kernel and userspace page tables. This separation + helps prevent user-space applications from accessing + kernel space memory, which is crucial for maintaining + system stability and security. + Performance impact on system call, context switch. + ''; + type = lib.types.bool; + default = false; + }; + randomizePageFreeList = lib.mkOption { + description = '' + Randomize free memory pages managed by the page allocator. + This randomization lowers risk against certain lib.types of attacks + that exploit predictable memory allocation patterns. + May slightly impact performance, may increase boot time. + ''; + type = lib.types.bool; + default = false; + }; + randomizeKStackOffset = lib.mkOption { + description = '' + Randomizes the offset of the kernel stack to enhance security + against certain lib.types of attacks, such as stack-based buffer + overflows or exploits that rely on knowing the exact layout of + the kernel stack. + May slightly impact performance. + ''; + type = lib.types.bool; + default = false; + }; + }; + }; + }; + config = lib.mkMerge [ + ## User account security + { + # root account + nix = { + settings.allowed-users = ["root"]; + }; + + # There is no possible string to hash to just “!” + users.users.root = lib.mkIf (!cfg.users.root.enable) { + shell = "${pkgs.shadow}/bin/nologin"; + }; + + # Enforce strong password + security.pam = { + services = let + minlen = config.ghaf.security.users.strong-password.min-passwd-len; + in { + passwd = lib.mkIf cfg.users.strong-password.enable { + text = '' + auth required pam_unix.so shadow nullok + auth required pam_faillock.so authfail audit deny=3 unlock_time=900 + account required pam_unix.so + account sufficient pam_localuser.so + password requisite ${pkgs.libpwquality.lib}/lib/security/pam_pwquality.so retry=3 minlen=${toString minlen} dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1 enforce_for_root=true + password required pam_unix.so use_authtok shadow + session required pam_unix.so + ''; + }; + }; + }; + + ## sudo administartion + security.sudo = { + inherit (cfg.users.sudo) enable; + inherit (cfg.users.sudo) extraConfig; + }; + } + + ## Linux security + (lib.mkIf cfg.system-security.enable { + services.openssh = { + extraConfig = lib.optionalString config.ghaf.profiles.release.enable '' + AllowTcpForwarding yes + X11Forwarding no + AllowAgentForwarding no + AllowStreamLocalForwarding no + AuthenticationMethods publickey + ''; + }; + + # Disable loading and execution of new kernel + security.protectKernelImage = lib.mkDefault config.ghaf.profiles.release.enable; + + # Disable user namespace cloning for unprivileged users + security.unprivilegedUsernsClone = lib.mkDefault false; + + # Disable Hyperthreading (To reduce risk of side channel attack) + security.allowSimultaneousMultithreading = lib.mkDefault (!(cfg.system-security.misc.disableHyperthreading || cfg.system-security.misc.enable-all)); + + # Flush L1 Data cache before entering guest vm + security.virtualisation.flushL1DataCache = lib.mkIf (cfg.system-security.misc.vm-flushL1DCache || cfg.system-security.misc.enable-all) (lib.mkDefault "always"); + + # Enforce page table isolation + security.forcePageTableIsolation = lib.mkDefault (cfg.system-security.misc.isolatePageTables || cfg.system-security.misc.enable-all); + + # Disable dynamic kernel modules + security.lockKernelModules = lib.mkDefault cfg.system-security.lock-kernel-modules; + + environment.memoryAllocator.provider = cfg.system-security.memory-allocator; + environment.variables = lib.mkIf (cfg.system-security.memory-allocator == "scudo") { + SCUDO_OPTIONS = lib.mkDefault "ZeroContents=1"; + }; + + boot.tmp.useTmpfs = lib.mkForce true; + + boot.kernel.sysctl = { + # Disable loading of new kernel + "kernel.kexec_load_disabled" = lib.mkForce config.ghaf.profiles.release.enable; + + # Disable ptrace + "kernel.yama.ptrace_scope" = lib.mkForce 3; + + # Completely hide kernel pointers + "kernel.kptr_restrict" = lib.mkForce 2; + + # Disable ftrace + "kernel.ftrace_enabled" = lib.mkDefault false; + + # Restrict core dump + "fs.suid_dumpable" = lib.mkForce 0; + + # Restrict kernel log + "kernel.dmesg_restrict" = lib.mkIf config.ghaf.profiles.release.enable (lib.mkForce 1); + + # Disable user space page fault handling + "vm.unprivileged_userfaultfd" = lib.mkForce 0; + + # Disable SysRq key + "kernel.sysrq" = lib.mkIf config.ghaf.profiles.release.enable (lib.mkForce 0); + + # Disable loading of line descipline kernel module of TTY device + # The line descipline module provides an interface between the low-level driver handling a TTY device + # and user terminal application + "dev.tty.ldisc_autoload" = lib.mkForce 0; + + # This will avoid unintentional writes to an attacker-controlled FIFO/Regular file. + # Extend the restriction to group sticky directories + "fs.protected_fifos" = lib.mkForce 2; + "fs.protected_regular" = lib.mkForce 2; + + # Allow only root to access perf events + "kernel.perf_event_paranoid" = lib.mkForce 3; + }; + + boot.blacklistedKernelModules = [ + # Obscure network protocols + "ax25" + "netrom" + "rose" + + # Old or rare or insufficiently audited filesystems + "adfs" + "affs" + "bfs" + "befs" + "cramfs" + "efs" + "erofs" + "exofs" + "freevxfs" + "f2fs" + "hfs" + "hpfs" + "jfs" + "minix" + "nilfs2" + "ntfs" + "omfs" + "qnx4" + "qnx6" + "sysv" + "ufs" + ]; + boot.kernelParams = + [ + # Fill freed pages and heap objects with zeroes + "init_on_free=1" + "init_on_alloc=1" + + # Panic on any uncorrectable errors through the machine check exception system + "mce=0" + ] + ++ lib.optionals (cfg.system-security.misc.randomizePageFreeList || cfg.system-security.misc.enable-all) [ + # Page allocation randomization + "page_alloc.shuffle=1" + ] + ++ lib.optionals (cfg.system-security.misc.randomizeKStackOffset || cfg.system-security.misc.enable-all) [ + # Kernel stack offset randomization + "randomize_kstack_offset=on" + ] + ++ lib.optionals config.ghaf.profiles.debug.enable [ + # To identify and fix potential vulnerability. + "slub_debug=FZPU" + ] + ++ lib.optionals config.ghaf.security.network.ipsecurity.enable [ + # Disable IPv6 to reduce attack surface. + "ipv6.disable=1" + ]; + }) + ]; +} diff --git a/modules/microvm/virtualization/microvm/adminvm.nix b/modules/microvm/virtualization/microvm/adminvm.nix index 387b39824..c78140839 100644 --- a/modules/microvm/virtualization/microvm/adminvm.nix +++ b/modules/microvm/virtualization/microvm/adminvm.nix @@ -32,6 +32,12 @@ withPolkit = true; withDebug = configHost.ghaf.profiles.debug.enable; }; + security = { + system-security.enable = true; + system-security.lock-kernel-modules = lib.mkDefault configHost.ghaf.profiles.release.enable; + network.ipsecurity.enable = true; + network.bpf-access-level = lib.mkForce 1; # Provide BPF access to privileged users + }; }; system.stateVersion = lib.trivial.release; diff --git a/modules/microvm/virtualization/microvm/appvm.nix b/modules/microvm/virtualization/microvm/appvm.nix index a8fb2256c..2fcabe19a 100644 --- a/modules/microvm/virtualization/microvm/appvm.nix +++ b/modules/microvm/virtualization/microvm/appvm.nix @@ -65,6 +65,12 @@ withDebug = configHost.ghaf.profiles.debug.enable; withHardenedConfigs = true; }; + security = { + system-security.enable = true; + system-security.lock-kernel-modules = lib.mkDefault configHost.ghaf.profiles.release.enable; + network.ipsecurity.enable = true; + network.bpf-access-level = lib.mkForce 1; # Provide BPF access to privileged users + }; }; # SSH is very picky about the file permissions and ownership and will diff --git a/modules/microvm/virtualization/microvm/audiovm.nix b/modules/microvm/virtualization/microvm/audiovm.nix index d1df382d8..5d084702d 100644 --- a/modules/microvm/virtualization/microvm/audiovm.nix +++ b/modules/microvm/virtualization/microvm/audiovm.nix @@ -38,6 +38,12 @@ withDebug = configHost.ghaf.profiles.debug.enable; }; services.audio.enable = true; + security = { + system-security.enable = true; + system-security.lock-kernel-modules = lib.mkDefault configHost.ghaf.profiles.release.enable; + network.ipsecurity.enable = true; + network.bpf-access-level = lib.mkForce 1; # Provide BPF access to privileged users + }; }; environment = { diff --git a/modules/microvm/virtualization/microvm/guivm.nix b/modules/microvm/virtualization/microvm/guivm.nix index 21fdd49ce..a3e05b310 100644 --- a/modules/microvm/virtualization/microvm/guivm.nix +++ b/modules/microvm/virtualization/microvm/guivm.nix @@ -45,6 +45,13 @@ withDebug = configHost.ghaf.profiles.debug.enable; withHardenedConfigs = true; }; + + security = { + system-security.enable = true; + system-security.lock-kernel-modules = lib.mkDefault configHost.ghaf.profiles.release.enable; + network.ipsecurity.enable = true; + network.bpf-access-level = lib.mkForce 1; # Provide BPF access to privileged users + }; }; systemd.services."waypipe-ssh-keygen" = let diff --git a/modules/microvm/virtualization/microvm/microvm-host.nix b/modules/microvm/virtualization/microvm/microvm-host.nix index 4f004d98d..0dbc9676b 100644 --- a/modules/microvm/virtualization/microvm/microvm-host.nix +++ b/modules/microvm/virtualization/microvm/microvm-host.nix @@ -15,21 +15,29 @@ in { config = lib.mkIf cfg.enable { microvm.host.enable = true; - ghaf.systemd = { - withName = "host-systemd"; - enable = true; - boot.enable = true; - withPolkit = true; - withTpm2Tss = pkgs.stdenv.hostPlatform.isx86; - withRepart = true; - withFido2 = true; - withCryptsetup = true; - withTimesyncd = cfg.networkSupport; - withNss = cfg.networkSupport; - withResolved = cfg.networkSupport; - withSerial = config.ghaf.profiles.debug.enable; - withDebug = config.ghaf.profiles.debug.enable; - withHardenedConfigs = true; + ghaf = { + systemd = { + withName = "host-systemd"; + enable = true; + boot.enable = true; + withPolkit = true; + withTpm2Tss = pkgs.stdenv.hostPlatform.isx86; + withRepart = true; + withFido2 = true; + withCryptsetup = true; + withTimesyncd = cfg.networkSupport; + withNss = cfg.networkSupport; + withResolved = cfg.networkSupport; + withSerial = config.ghaf.profiles.debug.enable; + withDebug = config.ghaf.profiles.debug.enable; + withHardenedConfigs = true; + }; + security = { + system-security.enable = true; + system-security.lock-kernel-modules = lib.mkDefault config.ghaf.profiles.release.enable; + network.ipsecurity.enable = true; + network.bpf-access-level = lib.mkForce 1; # Provide BPF access to privileged users + }; }; # TODO: remove hardcoded paths diff --git a/modules/microvm/virtualization/microvm/netvm.nix b/modules/microvm/virtualization/microvm/netvm.nix index 8b43c4525..0776dd404 100644 --- a/modules/microvm/virtualization/microvm/netvm.nix +++ b/modules/microvm/virtualization/microvm/netvm.nix @@ -43,6 +43,13 @@ withDebug = configHost.ghaf.profiles.debug.enable; withHardenedConfigs = true; }; + security = { + system-security.enable = true; + system-security.lock-kernel-modules = lib.mkDefault configHost.ghaf.profiles.release.enable; + network.ipsecurity.enable = true; + network.bpf-access-level = lib.mkForce 1; # Provide BPF access to privileged users + fail2ban.enable = true; + }; }; system.stateVersion = lib.trivial.release; diff --git a/modules/reference/appvms/chromium.nix b/modules/reference/appvms/chromium.nix index 86d07860a..10b697b10 100644 --- a/modules/reference/appvms/chromium.nix +++ b/modules/reference/appvms/chromium.nix @@ -61,6 +61,8 @@ in { microvm.devices = []; ghaf.programs.chromium.enable = true; + ghaf.security.apparmor.enable = true; + ghaf.security.apparmor.apps.chromium.enable = true; # Set default PDF XDG handler xdg.mime.defaultApplications."application/pdf" = "ghaf-pdf.desktop";