at 24.11-pre 9.1 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 inherit (builtins) attrNames head map match readFile; 7 inherit (lib) types; 8 inherit (config.environment) etc; 9 cfg = config.security.apparmor; 10 mkDisableOption = name: mkEnableOption name // { 11 default = true; 12 example = false; 13 }; 14 enabledPolicies = filterAttrs (n: p: p.enable) cfg.policies; 15in 16 17{ 18 imports = [ 19 (mkRemovedOptionModule [ "security" "apparmor" "confineSUIDApplications" ] "Please use the new options: `security.apparmor.policies.<policy>.enable'.") 20 (mkRemovedOptionModule [ "security" "apparmor" "profiles" ] "Please use the new option: `security.apparmor.policies'.") 21 apparmor/includes.nix 22 apparmor/profiles.nix 23 ]; 24 25 options = { 26 security.apparmor = { 27 enable = mkEnableOption '' 28 the AppArmor Mandatory Access Control system. 29 30 If you're enabling this module on a running system, 31 note that a reboot will be required to activate AppArmor in the kernel. 32 33 Also, beware that enabling this module privileges stability over security 34 by not trying to kill unconfined but newly confinable running processes by default, 35 though it would be needed because AppArmor can only confine new 36 or already confined processes of an executable. 37 This killing would for instance be necessary when upgrading to a NixOS revision 38 introducing for the first time an AppArmor profile for the executable 39 of a running process. 40 41 Enable [](#opt-security.apparmor.killUnconfinedConfinables) 42 if you want this service to do such killing 43 by sending a `SIGTERM` to those running processes''; 44 policies = mkOption { 45 description = '' 46 AppArmor policies. 47 ''; 48 type = types.attrsOf (types.submodule ({ name, config, ... }: { 49 options = { 50 enable = mkDisableOption "loading of the profile into the kernel"; 51 enforce = mkDisableOption "enforcing of the policy or only complain in the logs"; 52 profile = mkOption { 53 description = "The policy of the profile."; 54 type = types.lines; 55 apply = pkgs.writeText name; 56 }; 57 }; 58 })); 59 default = {}; 60 }; 61 includes = mkOption { 62 type = types.attrsOf types.lines; 63 default = {}; 64 description = '' 65 List of paths to be added to AppArmor's searched paths 66 when resolving `include` directives. 67 ''; 68 apply = mapAttrs pkgs.writeText; 69 }; 70 packages = mkOption { 71 type = types.listOf types.package; 72 default = []; 73 description = "List of packages to be added to AppArmor's include path"; 74 }; 75 enableCache = mkEnableOption '' 76 caching of AppArmor policies 77 in `/var/cache/apparmor/`. 78 79 Beware that AppArmor policies almost always contain Nix store paths, 80 and thus produce at each change of these paths 81 a new cached version accumulating in the cache''; 82 killUnconfinedConfinables = mkEnableOption '' 83 killing of processes which have an AppArmor profile enabled 84 (in [](#opt-security.apparmor.policies)) 85 but are not confined (because AppArmor can only confine new processes). 86 87 This is only sending a gracious `SIGTERM` signal to the processes, 88 not a `SIGKILL`. 89 90 Beware that due to a current limitation of AppArmor, 91 only profiles with exact paths (and no name) can enable such kills''; 92 }; 93 }; 94 95 config = mkIf cfg.enable { 96 assertions = map (policy: 97 { assertion = match ".*/.*" policy == null; 98 message = "`security.apparmor.policies.\"${policy}\"' must not contain a slash."; 99 # Because, for instance, aa-remove-unknown uses profiles_names_list() in rc.apparmor.functions 100 # which does not recurse into sub-directories. 101 } 102 ) (attrNames cfg.policies); 103 104 environment.systemPackages = [ 105 pkgs.apparmor-utils 106 pkgs.apparmor-bin-utils 107 ]; 108 environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" ( 109 # It's important to put only enabledPolicies here and not all cfg.policies 110 # because aa-remove-unknown reads profiles from all /etc/apparmor.d/* 111 mapAttrsToList (name: p: { inherit name; path = p.profile; }) enabledPolicies ++ 112 mapAttrsToList (name: path: { inherit name path; }) cfg.includes 113 ); 114 environment.etc."apparmor/parser.conf".text = '' 115 ${if cfg.enableCache then "write-cache" else "skip-cache"} 116 cache-loc /var/cache/apparmor 117 Include /etc/apparmor.d 118 '' + 119 concatMapStrings (p: "Include ${p}/etc/apparmor.d\n") cfg.packages; 120 # For aa-logprof 121 environment.etc."apparmor/apparmor.conf".text = '' 122 ''; 123 # For aa-logprof 124 environment.etc."apparmor/severity.db".source = pkgs.apparmor-utils + "/etc/apparmor/severity.db"; 125 environment.etc."apparmor/logprof.conf".source = pkgs.runCommand "logprof.conf" { 126 header = '' 127 [settings] 128 # /etc/apparmor.d/ is read-only on NixOS 129 profiledir = /var/cache/apparmor/logprof 130 inactive_profiledir = /etc/apparmor.d/disable 131 # Use: journalctl -b --since today --grep audit: | aa-logprof 132 logfiles = /dev/stdin 133 134 parser = ${pkgs.apparmor-parser}/bin/apparmor_parser 135 ldd = ${pkgs.glibc.bin}/bin/ldd 136 logger = ${pkgs.util-linux}/bin/logger 137 138 # customize how file ownership permissions are presented 139 # 0 - off 140 # 1 - default of what ever mode the log reported 141 # 2 - force the new permissions to be user 142 # 3 - force all perms on the rule to be user 143 default_owner_prompt = 1 144 145 custom_includes = /etc/apparmor.d ${concatMapStringsSep " " (p: "${p}/etc/apparmor.d") cfg.packages} 146 147 [qualifiers] 148 ${pkgs.runtimeShell} = icnu 149 ${pkgs.bashInteractive}/bin/sh = icnu 150 ${pkgs.bashInteractive}/bin/bash = icnu 151 ${config.users.defaultUserShell} = icnu 152 ''; 153 footer = "${pkgs.apparmor-utils}/etc/apparmor/logprof.conf"; 154 passAsFile = [ "header" ]; 155 } '' 156 cp $headerPath $out 157 sed '1,/\[qualifiers\]/d' $footer >> $out 158 ''; 159 160 boot.kernelParams = [ "apparmor=1" "security=apparmor" ]; 161 162 systemd.services.apparmor = { 163 after = [ 164 "local-fs.target" 165 "systemd-journald-audit.socket" 166 ]; 167 before = [ "sysinit.target" "shutdown.target" ]; 168 conflicts = [ "shutdown.target" ]; 169 wantedBy = [ "multi-user.target" ]; 170 unitConfig = { 171 Description="Load AppArmor policies"; 172 DefaultDependencies = "no"; 173 ConditionSecurity = "apparmor"; 174 }; 175 # Reloading instead of restarting enables to load new AppArmor profiles 176 # without necessarily restarting all services which have Requires=apparmor.service 177 reloadIfChanged = true; 178 restartTriggers = [ 179 etc."apparmor/parser.conf".source 180 etc."apparmor.d".source 181 ]; 182 serviceConfig = let 183 killUnconfinedConfinables = pkgs.writeShellScript "apparmor-kill" '' 184 set -eu 185 ${pkgs.apparmor-bin-utils}/bin/aa-status --json | 186 ${pkgs.jq}/bin/jq --raw-output '.processes | .[] | .[] | select (.status == "unconfined") | .pid' | 187 xargs --verbose --no-run-if-empty --delimiter='\n' \ 188 kill 189 ''; 190 commonOpts = p: "--verbose --show-cache ${optionalString (!p.enforce) "--complain "}${p.profile}"; 191 in { 192 Type = "oneshot"; 193 RemainAfterExit = "yes"; 194 ExecStartPre = "${pkgs.apparmor-utils}/bin/aa-teardown"; 195 ExecStart = mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}") enabledPolicies; 196 ExecStartPost = optional cfg.killUnconfinedConfinables killUnconfinedConfinables; 197 ExecReload = 198 # Add or replace into the kernel profiles in enabledPolicies 199 # (because AppArmor can do that without stopping the processes already confined). 200 mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}") enabledPolicies ++ 201 # Remove from the kernel any profile whose name is not 202 # one of the names within the content of the profiles in enabledPolicies 203 # (indirectly read from /etc/apparmor.d/*, without recursing into sub-directory). 204 # Note that this does not remove profiles dynamically generated by libvirt. 205 [ "${pkgs.apparmor-utils}/bin/aa-remove-unknown" ] ++ 206 # Optionally kill the processes which are unconfined but now have a profile loaded 207 # (because AppArmor can only start to confine new processes). 208 optional cfg.killUnconfinedConfinables killUnconfinedConfinables; 209 ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown"; 210 CacheDirectory = [ "apparmor" "apparmor/logprof" ]; 211 CacheDirectoryMode = "0700"; 212 }; 213 }; 214 }; 215 216 meta.maintainers = with maintainers; [ julm ]; 217}