- vendor modules/anssi/ depuis SécurixOS (DINUM), copy-paste explicitement supporté par son propre README, licence MIT - hosts/common/base.nix: security.anssi.enable, level=intermediary, category=client -- remplace le bloc sysctl qui n'existait que sur le profil oreo (nixos_users), même valeurs, rien de plus fort - garde systemd.coredump.enable=false + apparmor (pas couvert par le module ANSSI, exception R45 explicite côté Sécurix) - ip_forward=0 (R12) n'affecte pas pxe-server, qui n'importe pas ce fichier Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
325 lines
11 KiB
Nix
325 lines
11 KiB
Nix
# SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||
#
|
||
# SPDX-License-Identifier: MIT
|
||
|
||
let
|
||
mkSysctlChecker =
|
||
lib: attrs:
|
||
let
|
||
# Normalize whitespace (tabs to spaces, collapse runs, trim edges)
|
||
# so that sysctl output like "32768\t65535" matches Nix value "32768 65535".
|
||
normalizeWhitespace = "tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//' ";
|
||
mkCheckSingularSysctl = attr: expectedValue: ''
|
||
# Check for sysctl '${attr}'
|
||
actual_value=$(sysctl -n "${attr}" | ${normalizeWhitespace}) || {
|
||
echo "Check failed for ${attr}: unable to read sysctl value"
|
||
exit 1
|
||
}
|
||
expected_value=$(echo "${toString expectedValue}" | ${normalizeWhitespace})
|
||
if [[ "$actual_value" != "$expected_value" ]]; then
|
||
echo "Check failed for ${attr}: expected $expected_value, got $actual_value"
|
||
exit 1
|
||
else
|
||
echo "Check passed for ${attr}"
|
||
fi
|
||
'';
|
||
in
|
||
''
|
||
${lib.concatStringsSep "\n" (
|
||
map (name: mkCheckSingularSysctl name attrs.${name}) (lib.attrNames attrs)
|
||
)}
|
||
'';
|
||
in
|
||
{
|
||
R8 = {
|
||
name = "R8_MemoryBootOptions";
|
||
anssiRef = "R8 – Paramétrer les options de configuration de la mémoire";
|
||
description = "Set memory options at boot";
|
||
severity = "intermediary";
|
||
category = "base";
|
||
|
||
config = _: {
|
||
boot.kernelParams = [
|
||
"l1tf=full,force" # Enable full mitigation for L1 Terminal Fault (L1TF), including disabling SMT
|
||
"page_poison=on" # Enable poisoning of freed pages to detect leaks
|
||
"pti=on" # Force use of Page Table Isolation (PTI) for Meltdown vulnerability
|
||
"slab_nomerge=yes" # Disable slab cache merging to complicate heap overflow attacks
|
||
"slub_debug=FZP" # Enable slab cache debugging with specific tests
|
||
"spec_store_bypass_disable=seccomp" # Use seccomp mitigation for Spectre v4
|
||
"spectre_v2=on" # Force Spectre v2 mitigation
|
||
"mds=full,nosmt" # Enable full mitigation for MDS and disable SMT (requires microcode update)
|
||
"mce=0" # Force kernel panic on uncorrected Machine Check exceptions
|
||
"page_alloc.shuffle=1" # Enable page allocator randomization
|
||
"rng_core.default_quality=500" # Set HWRNG quality for TPM-based CSPRNG initialization
|
||
];
|
||
};
|
||
|
||
checkScript =
|
||
pkgs:
|
||
pkgs.writeShellScript "check-R8" ''
|
||
# The kernel boot parameters we expect to check
|
||
EXPECTED_PARAMS=(
|
||
"l1tf=full,force"
|
||
"page_poison=on"
|
||
"pti=on"
|
||
"slab_nomerge=yes"
|
||
"slub_debug=FZP"
|
||
"spec_store_bypass_disable=seccomp"
|
||
"spectre_v2=on"
|
||
"mds=full,nosmt"
|
||
"mce=0"
|
||
"page_alloc.shuffle=1"
|
||
"rng_core.default_quality=500"
|
||
)
|
||
|
||
# Get the current kernel parameters from /proc/cmdline
|
||
ACTUAL_PARAMS=$(cat /proc/cmdline)
|
||
|
||
# Check if each expected parameter is in the actual kernel parameters
|
||
for param in "''${EXPECTED_PARAMS[@]}"; do
|
||
if echo "$ACTUAL_PARAMS" | grep -q "$param"; then
|
||
echo "Kernel parameter '$param' is correctly set."
|
||
else
|
||
echo "WARNING: Kernel parameter '$param' is NOT set."
|
||
exit 1
|
||
fi
|
||
done
|
||
|
||
# Additionally, check specific parameters from /proc/cpuinfo (for cpu-related parameters)
|
||
echo "Checking CPU-specific parameters..."
|
||
|
||
# Check if SMT (Hyper-Threading) is disabled, as required by "mds=full,nosmt"
|
||
if grep -q "nosmt" <<< "$ACTUAL_PARAMS"; then
|
||
if grep -q "siblings" /proc/cpuinfo && ! grep -q "HT" /proc/cpuinfo; then
|
||
echo "SMT is disabled (as expected for mds=full,nosmt)."
|
||
else
|
||
echo "WARNING: SMT is still enabled, but 'mds=full,nosmt' was passed."
|
||
fi
|
||
fi
|
||
|
||
# Check if Page Table Isolation (PTI) is enabled (from /proc/cpuinfo)
|
||
if grep -q "pti" <<< "$ACTUAL_PARAMS"; then
|
||
if grep -q "pti" /proc/cpuinfo; then
|
||
echo "PTI is enabled (as expected for pti=on)."
|
||
else
|
||
echo "WARNING: PTI is not enabled, despite pti=on being passed."
|
||
fi
|
||
fi
|
||
|
||
exit 0
|
||
'';
|
||
};
|
||
|
||
R9 =
|
||
let
|
||
sysctls = {
|
||
# Restrict access to dmesg buffer
|
||
"kernel.dmesg_restrict" = "1";
|
||
|
||
# Hide kernel addresses in /proc and other interfaces, even for privileged users
|
||
"kernel.kptr_restrict" = "2";
|
||
|
||
# Set process ID maximum
|
||
"kernel.pid_max" = "1048576"; # 2^20
|
||
|
||
# Restrict perf subsystem usage
|
||
"kernel.perf_cpu_time_max_percent" = "1";
|
||
"kernel.perf_event_max_sample_rate" = "1";
|
||
|
||
# Restrict access to perf_event_open system call
|
||
"kernel.perf_event_paranoid" = "2";
|
||
|
||
# Enable Address Space Layout Randomization (ASLR)
|
||
"kernel.randomize_va_space" = "2";
|
||
|
||
# Disable Magic SysRq key (prevents magical system requests)
|
||
"kernel.sysrq" = "0";
|
||
|
||
# Restrict use of BPF (Berkeley Packet Filter) to privileged users
|
||
"kernel.unprivileged_bpf_disabled" = "1";
|
||
|
||
# Kernel panic on unexpected kernel behavior (e.g., oops)
|
||
"kernel.panic_on_oops" = "1";
|
||
};
|
||
in
|
||
{
|
||
name = "R9_KernelOptions";
|
||
anssiRef = "R9 – Paramétrer les options de configuration du noyau";
|
||
description = "Tune kernel configuration options";
|
||
severity = "intermediary";
|
||
category = "base";
|
||
|
||
config = _: { boot.kernel.sysctl = sysctls; };
|
||
|
||
checkScript =
|
||
pkgs:
|
||
pkgs.writeShellScript "check-R9" ''
|
||
${mkSysctlChecker pkgs.lib sysctls}
|
||
'';
|
||
};
|
||
|
||
R10 = {
|
||
name = "R10_DisableKernelModuleLoading";
|
||
anssiRef = "R10 – Désactiver le chargement des modules noyau";
|
||
description = "Disable loading of kernel modules";
|
||
severity = "reinforced";
|
||
category = "base";
|
||
tags = [ "disable-kernel-module-loading" ];
|
||
|
||
config = _: { boot.kernel.sysctl."kernel.modules_disabled" = "1"; };
|
||
|
||
checkScript =
|
||
pkgs:
|
||
pkgs.writeShellScript "check-R10" ''
|
||
${mkSysctlChecker pkgs.lib { "kernel.modules_disabled" = 1; }}
|
||
'';
|
||
};
|
||
|
||
R11 = {
|
||
name = "R11_ConfigureYamaLSM";
|
||
anssiRef = "R11 – Activer et configurer le LSM Yama";
|
||
description = "Enable and configure the Yama Linux Security Module";
|
||
severity = "intermediary";
|
||
category = "base";
|
||
|
||
config = _: { boot.kernel.sysctl."kernel.yama.ptrace_scope" = "1"; };
|
||
|
||
checkScript =
|
||
pkgs:
|
||
pkgs.writeShellScript "check-R11" ''
|
||
${mkSysctlChecker pkgs.lib { "kernel.yama.ptrace_scope" = 1; }}
|
||
'';
|
||
};
|
||
|
||
R12 =
|
||
let
|
||
ipv4_sysctls = {
|
||
# Harden the JIT compiler for BPF
|
||
"net.core.bpf_jit_harden" = "2";
|
||
|
||
# Disable IP forwarding (no routing between interfaces)
|
||
"net.ipv4.ip_forward" = "0";
|
||
|
||
# Reject packets with source address 127/8 (loopback network)
|
||
"net.ipv4.conf.all.accept_local" = "0";
|
||
|
||
# Disable ICMP redirects (prevents potential traffic redirection)
|
||
"net.ipv4.conf.all.accept_redirects" = "0";
|
||
"net.ipv4.conf.default.accept_redirects" = "0";
|
||
"net.ipv4.conf.all.secure_redirects" = "0";
|
||
"net.ipv4.conf.default.secure_redirects" = "0";
|
||
|
||
# Disable shared media detection (useful for preventing misconfiguration)
|
||
"net.ipv4.conf.all.shared_media" = "0";
|
||
"net.ipv4.conf.default.shared_media" = "0";
|
||
|
||
# Disable kernel ARP management across interfaces (security for high-availability setups)
|
||
"net.ipv4.conf.all.arp_filter" = "1";
|
||
|
||
# Respond to ARP requests only if source and destination are on the same network
|
||
"net.ipv4.conf.all.arp_ignore" = "2";
|
||
|
||
# Refuse routing of packets with loopback addresses as source or destination
|
||
"net.ipv4.conf.all.route_localnet" = "0";
|
||
|
||
# Drop Gratuitous ARP (prevents ARP poisoning attacks)
|
||
"net.ipv4.conf.all.drop_gratuitous_arp" = "1";
|
||
|
||
# Perform Reverse Path Filtering (checks if the source is reachable via the interface it came from)
|
||
"net.ipv4.conf.default.rp_filter" = "1";
|
||
"net.ipv4.conf.all.rp_filter" = "1";
|
||
|
||
# Disable sending ICMP redirects (normal for routers but unnecessary for end hosts)
|
||
"net.ipv4.conf.default.send_redirects" = "0";
|
||
"net.ipv4.conf.all.send_redirects" = "0";
|
||
|
||
# Ignore bogus ICMP error responses (RFC 1122)
|
||
"net.ipv4.icmp_ignore_bogus_error_responses" = "1";
|
||
|
||
# Increase the range for ephemeral ports (for better security)
|
||
"net.ipv4.ip_local_port_range" = "32768 65535";
|
||
|
||
# Enable RFC 1337 compliance (prevents TCP timestamp attacks)
|
||
"net.ipv4.tcp_rfc1337" = "1";
|
||
|
||
# Enable SYN cookies (prevents SYN flood attacks)
|
||
"net.ipv4.tcp_syncookies" = "1";
|
||
};
|
||
in
|
||
{
|
||
name = "R12_IPv4Networking";
|
||
anssiRef = "R12 – Paramétrer les options de configuration du réseau IPv4";
|
||
description = "Configure IPv4 network options";
|
||
severity = "intermediary";
|
||
category = "base";
|
||
|
||
config = _: { boot.kernel.sysctl = ipv4_sysctls; };
|
||
|
||
checkScript =
|
||
pkgs:
|
||
pkgs.writeShellScript "check-R12" ''
|
||
${mkSysctlChecker pkgs.lib ipv4_sysctls}
|
||
'';
|
||
};
|
||
|
||
R13 =
|
||
let
|
||
sysctls = {
|
||
# Disable IPv6 on all interfaces by default
|
||
"net.ipv6.conf.default.disable_ipv6" = "1";
|
||
|
||
# Disable IPv6 on all interfaces
|
||
"net.ipv6.conf.all.disable_ipv6" = "1";
|
||
};
|
||
in
|
||
{
|
||
name = "R13_DisableIPv6";
|
||
anssiRef = "R13 – Désactiver le plan IPv6";
|
||
description = "Disable IPv6";
|
||
severity = "intermediary";
|
||
category = "base";
|
||
tags = [ "no-ipv6" ];
|
||
|
||
config = _: { boot.kernel.sysctl = sysctls; };
|
||
|
||
checkScript =
|
||
pkgs:
|
||
pkgs.writeShellScript "check-R13" ''
|
||
${mkSysctlChecker pkgs.lib sysctls}
|
||
'';
|
||
};
|
||
|
||
R14 =
|
||
let
|
||
sysctls = {
|
||
# Disable coredumps for setuid executables
|
||
"fs.suid_dumpable" = "0";
|
||
|
||
# Prevent opening FIFOs and regular files not owned by the user in sticky directories
|
||
"fs.protected_fifos" = "2";
|
||
"fs.protected_regular" = "2";
|
||
|
||
# Restrict the creation of symbolic links to files owned by the user
|
||
"fs.protected_symlinks" = "1";
|
||
|
||
# Restrict the creation of hard links to files owned by the user
|
||
"fs.protected_hardlinks" = "1";
|
||
};
|
||
in
|
||
{
|
||
name = "R14_FilesystemConfig";
|
||
anssiRef = "R14 – Paramétrer les options de configuration des systèmes de fichiers";
|
||
description = "Tune filesystem configuration options";
|
||
severity = "intermediary";
|
||
category = "base";
|
||
|
||
config = _: { boot.kernel.sysctl = sysctls; };
|
||
|
||
checkScript =
|
||
pkgs:
|
||
pkgs.writeShellScript "check-R14" ''
|
||
${mkSysctlChecker pkgs.lib sysctls}
|
||
'';
|
||
};
|
||
}
|