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