at 23.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 (lib.mdDoc 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 (lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc "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 = lib.mdDoc '' 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 = lib.mdDoc "List of packages to be added to AppArmor's include path"; 74 }; 75 enableCache = mkEnableOption (lib.mdDoc '' 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 (lib.mdDoc '' 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" ]; 168 wantedBy = [ "multi-user.target" ]; 169 unitConfig = { 170 Description="Load AppArmor policies"; 171 DefaultDependencies = "no"; 172 ConditionSecurity = "apparmor"; 173 }; 174 # Reloading instead of restarting enables to load new AppArmor profiles 175 # without necessarily restarting all services which have Requires=apparmor.service 176 reloadIfChanged = true; 177 restartTriggers = [ 178 etc."apparmor/parser.conf".source 179 etc."apparmor.d".source 180 ]; 181 serviceConfig = let 182 killUnconfinedConfinables = pkgs.writeShellScript "apparmor-kill" '' 183 set -eu 184 ${pkgs.apparmor-bin-utils}/bin/aa-status --json | 185 ${pkgs.jq}/bin/jq --raw-output '.processes | .[] | .[] | select (.status == "unconfined") | .pid' | 186 xargs --verbose --no-run-if-empty --delimiter='\n' \ 187 kill 188 ''; 189 commonOpts = p: "--verbose --show-cache ${optionalString (!p.enforce) "--complain "}${p.profile}"; 190 in { 191 Type = "oneshot"; 192 RemainAfterExit = "yes"; 193 ExecStartPre = "${pkgs.apparmor-utils}/bin/aa-teardown"; 194 ExecStart = mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}") enabledPolicies; 195 ExecStartPost = optional cfg.killUnconfinedConfinables killUnconfinedConfinables; 196 ExecReload = 197 # Add or replace into the kernel profiles in enabledPolicies 198 # (because AppArmor can do that without stopping the processes already confined). 199 mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}") enabledPolicies ++ 200 # Remove from the kernel any profile whose name is not 201 # one of the names within the content of the profiles in enabledPolicies 202 # (indirectly read from /etc/apparmor.d/*, without recursing into sub-directory). 203 # Note that this does not remove profiles dynamically generated by libvirt. 204 [ "${pkgs.apparmor-utils}/bin/aa-remove-unknown" ] ++ 205 # Optionally kill the processes which are unconfined but now have a profile loaded 206 # (because AppArmor can only start to confine new processes). 207 optional cfg.killUnconfinedConfinables killUnconfinedConfinables; 208 ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown"; 209 CacheDirectory = [ "apparmor" "apparmor/logprof" ]; 210 CacheDirectoryMode = "0700"; 211 }; 212 }; 213 }; 214 215 meta.maintainers = with maintainers; [ julm ]; 216}