]> Git — Sourcephile - sourcephile-nix.git/blob - nixos/modules/security/apparmor.nix
nix: use nixpkgs/patches/wip.diff instead of nixpkgs/overlays.nix and nixos/modules.nix
[sourcephile-nix.git] / nixos / modules / security / apparmor.nix
1 { config, lib, pkgs, ... }:
2
3 let
4 inherit (builtins) head match readFile;
5 inherit (lib) types;
6 inherit (config.environment) etc;
7 cfg = config.security.apparmor;
8 mkDisableOption = descr: lib.mkEnableOption descr // { default=true; example=false; };
9 in
10
11 {
12 imports = [
13 (lib.mkRemovedOptionModule [ "security" "apparmor" "profiles" ] "Please use the new security.apparmor.policies.")
14 apparmor/profiles.nix
15 ];
16 options = {
17 security.apparmor = {
18 enable = lib.mkEnableOption "Whether to enable the AppArmor Mandatory Access Control system.";
19 policies = lib.mkOption {
20 description = ''
21 AppArmor policies.
22 '';
23 type = types.attrsOf (types.submodule ({ name, config, ... }: {
24 options = {
25 enable = mkDisableOption "Whether to load the profile into the kernel.";
26 enforce = mkDisableOption "Whether to enforce the policy or only complain in the logs.";
27 profile = lib.mkOption {
28 description = "The policy of the profile.";
29 type = types.lines;
30 apply = pkgs.writeText name;
31 };
32 };
33 }));
34 default = {};
35 };
36 includes = lib.mkOption {
37 type = types.attrsOf types.lines;
38 default = [];
39 description = ''
40 List of paths to be added to AppArmor's searched paths
41 when resolving absolute #include directives.
42 '';
43 apply = lib.mapAttrs pkgs.writeText;
44 };
45 packages = lib.mkOption {
46 type = types.listOf types.package;
47 default = [];
48 description = "List of packages to be added to AppArmor's include path";
49 };
50 enableCache = lib.mkEnableOption ''
51 Whether to enable caching of AppArmor policies
52 in <literal>/var/cache/apparmor/</literal>.
53 Beware that AppArmor policies almost always contain Nix store paths,
54 and thus produce at each change of these paths
55 a new cached version accumulating in the cache.
56 '';
57 killUnconfinedConfinables = mkDisableOption ''
58 Whether to kill processes which have an AppArmor profile enabled
59 (in <link linkend="opt-security.apparmor.policies">policies</link>)
60 but are not confined (because AppArmor can only confine new processes).
61 '';
62 };
63 };
64
65 config = lib.mkIf cfg.enable {
66 environment.systemPackages = [ pkgs.apparmor-utils ];
67 environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" (
68 lib.mapAttrsToList (name: p: {inherit name; path=p.profile;}) cfg.policies ++
69 lib.mapAttrsToList (name: path: {inherit name path;}) cfg.includes
70 );
71 environment.etc."apparmor/parser.conf".text = ''
72 ${if cfg.enableCache then "write-cache" else "skip-cache"}
73 cache-loc /var/cache/apparmor
74 Include /etc/apparmor.d
75 '' +
76 lib.concatMapStrings (p: "Include ${p}/etc/apparmor.d\n") cfg.packages;
77 environment.etc."apparmor/logprof.conf".text = ''
78 [settings]
79 profiledir = /etc/apparmor.d
80 inactive_profiledir = ${pkgs.apparmor-profiles}/share/apparmor/extra-profiles
81 logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages
82
83 parser = ${pkgs.apparmor-parser}/bin/apparmor_parser
84 ldd = ${pkgs.glibc.bin}/bin/ldd
85 logger = ${pkgs.utillinux}/bin/logger
86
87 # customize how file ownership permissions are presented
88 # 0 - off
89 # 1 - default of what ever mode the log reported
90 # 2 - force the new permissions to be user
91 # 3 - force all perms on the rule to be user
92 default_owner_prompt = 1
93
94 # custom directory locations to look for #includes
95 #
96 # each name should be a valid directory containing possible #include
97 # candidate files under the profile dir which by default is /etc/apparmor.d.
98 #
99 # So an entry of my-includes will allow /etc/apparmor.d/my-includes to
100 # be used by the yast UI and profiling tools as a source of #include
101 # files.
102 custom_includes =
103
104 [qualifiers]
105 ${pkgs.runtimeShell} = icnu
106 ${pkgs.bashInteractive}/bin/sh = icnu
107 ${pkgs.bashInteractive}/bin/bash = icnu
108 '' + head (match "^.*\\[qualifiers](.*)" # Drop the original [settings] section.
109 (readFile "${pkgs.apparmor-utils}/etc/apparmor/logprof.conf"));
110
111 boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
112
113 systemd.services.apparmor = {
114 after = [
115 "local-fs.target"
116 "systemd-journald-audit.socket"
117 ];
118 before = [ "sysinit.target" ];
119 wantedBy = [ "multi-user.target" ];
120 restartTriggers = [
121 etc."apparmor/parser.conf".source
122 etc."apparmor.d".source
123 ];
124 unitConfig = {
125 Description="Load AppArmor policies";
126 DefaultDependencies = "no";
127 ConditionSecurity = "apparmor";
128 };
129 # Reloading instead of restarting enables to load new AppArmor profiles
130 # without necessarily restarting all services which have Requires=apparmor.service
131 # It works by:
132 # - Adding or replacing into the kernel profiles enabled in cfg.policies
133 # (because AppArmor can do that without stopping the processes already confined).
134 # - Removing from the kernel any profile whose name is not
135 # one of the names within the content of the profiles in cfg.policies.
136 # - Killing the processes which are unconfined but now have a profile loaded
137 # (because AppArmor can only confine new processes).
138 reloadIfChanged = true;
139 # Avoid searchs in /usr/share/locale/
140 environment.LANG="C";
141 serviceConfig = let
142 enabledPolicies = lib.attrValues (lib.filterAttrs (n: p: p.enable) cfg.policies);
143 unloadDisabledProfiles = pkgs.writeShellScript "apparmor-remove" ''
144 set -eux
145
146 enabledProfiles=$(mktemp)
147 loadedProfiles=$(mktemp)
148 trap "rm -f $enabledProfiles $loadedProfiles" EXIT
149
150 ${pkgs.apparmor-parser}/bin/apparmor_parser --names /dev/null ${
151 lib.concatMapStrings (p: "\\\n "+p.profile) enabledPolicies} |
152 sort -u >"$enabledProfiles"
153
154 sed -e "s/ (\(enforce\|complain\))$//" /sys/kernel/security/apparmor/profiles |
155 sort -u >"$loadedProfiles"
156
157 comm -23 "$loadedProfiles" "$enabledProfiles" |
158 while IFS=$'\n\r' read -r profile
159 do printf %s "$profile" >/sys/kernel/security/apparmor/.remove
160 done
161 '';
162 killUnconfinedConfinables = pkgs.writeShellScript "apparmor-kill" ''
163 set -eux
164 ${pkgs.apparmor-utils}/bin/aa-status --json |
165 ${pkgs.jq}/bin/jq --raw-output '.processes | .[] | .[] | select (.status == "unconfined") | .pid' |
166 xargs --verbose --no-run-if-empty --delimiter='\n' \
167 kill
168 '';
169 commonOpts = p: "--verbose --show-cache ${lib.optionalString (!p.enforce) "--complain "}${p.profile}";
170 in {
171 Type = "oneshot";
172 RemainAfterExit = "yes";
173 ExecStartPre = "${pkgs.apparmor-utils}/bin/aa-teardown";
174 ExecStart = map (p: "${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}") enabledPolicies;
175 ExecStartPost = lib.optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
176 ExecReload =
177 map (p: "${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}") enabledPolicies ++
178 [ unloadDisabledProfiles ] ++
179 lib.optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
180 ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown";
181 CacheDirectory = [ "apparmor" ];
182 CacheDirectoryMode = "0700";
183 };
184 };
185 };
186
187 meta.maintainers = with lib.maintainers; [ julm ];
188 }