Skip to content

Commit fb6d63f

Browse files
committed
apparmor: fix and improve the service
1 parent 539ae5c commit fb6d63f

20 files changed

Lines changed: 796 additions & 167 deletions

File tree

maintainers/maintainer-list.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4169,7 +4169,7 @@
41694169
name = "Julien Dehos";
41704170
};
41714171
julm = {
4172-
email = "julm+nix@sourcephile.fr";
4172+
email = "julm+nixpkgs@sourcephile.fr";
41734173
github = "ju1m";
41744174
githubId = 21160136;
41754175
name = "Julien Moutinho";

nixos/doc/manual/release-notes/rl-2009.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,24 @@ services.transmission.settings.rpc-bind-address = "0.0.0.0";
910910
to get the previous behavior of listening on all network interfaces.
911911
</para>
912912
</listitem>
913+
<listitem>
914+
<para>
915+
The <literal>security.apparmor</literal> module,
916+
for the <link xlink:href="https://gitlab.com/apparmor/apparmor/-/wikis/Documentation">AppArmor</link>
917+
Mandatory Access Control system,
918+
has been substantialy improved along with related tools,
919+
so that module maintainers can now more easily write AppArmor profiles for NixOS.
920+
The most notable change on the user-side is the new option <xref linkend="opt-security.apparmor.policies"/>,
921+
replacing the previous <literal>profiles</literal> option
922+
to provide a way to disable a profile
923+
and to select whether to confine in enforce mode (default)
924+
or in complain mode (see <literal>journalctl -b --grep apparmor</literal>).
925+
Before enabling this module, either directly
926+
or by importing <literal>&lt;nixpkgs/nixos/modules/profiles/hardened.nix&gt;</literal>,
927+
please be sure to read the documentation of <link linkend="opt-security.apparmor.enable">security.apparmor.enable</link>,
928+
and especially the part about <xref linkend="opt-security.apparmor.killUnconfinedConfinables"/>.
929+
</para>
930+
</listitem>
913931
<listitem>
914932
<para>
915933
With this release <literal>systemd-networkd</literal> (when enabled through <xref linkend="opt-networking.useNetworkd"/>)

nixos/modules/config/fonts/fontconfig.nix

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,40 @@ in
449449
(mkIf cfg.enable {
450450
environment.systemPackages = [ pkgs.fontconfig ];
451451
environment.etc.fonts.source = "${fontconfigEtc}/etc/fonts/";
452+
security.apparmor.includes."abstractions/fonts" = ''
453+
# fonts.conf
454+
r ${pkg.out}/etc/fonts/fonts.conf,
455+
456+
# fontconfig default config files
457+
r ${pkg.out}/etc/fonts/conf.d/*.conf,
458+
459+
# 00-nixos-cache.conf
460+
r ${cacheConf},
461+
462+
# 10-nixos-rendering.conf
463+
r ${renderConf},
464+
465+
# 50-user.conf
466+
${optionalString cfg.includeUserConf ''
467+
r ${pkg.out}/etc/fonts/conf.d.bak/50-user.conf,
468+
''}
469+
470+
# local.conf (indirect priority 51)
471+
${optionalString (cfg.localConf != "") ''
472+
r ${localConf},
473+
''}
474+
475+
# 52-nixos-default-fonts.conf
476+
r ${defaultFontsConf},
477+
478+
# 53-no-bitmaps.conf
479+
r ${rejectBitmaps},
480+
481+
${optionalString (!cfg.allowType1) ''
482+
# 53-nixos-reject-type1.conf
483+
r ${rejectType1},
484+
''}
485+
'';
452486
})
453487
(mkIf cfg.enable {
454488
fonts.fontconfig.confPackages = [ confPkg ];

nixos/modules/config/malloc.nix

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,12 @@ in
8787
environment.etc."ld-nix.so.preload".text = ''
8888
${providerLibPath}
8989
'';
90+
security.apparmor.includes = {
91+
"abstractions/base" = ''
92+
r /etc/ld-nix.so.preload,
93+
r ${config.environment.etc."ld-nix.so.preload".source},
94+
mr ${providerLibPath},
95+
'';
96+
};
9097
};
9198
}

nixos/modules/module-list.nix

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@
183183
./rename.nix
184184
./security/acme.nix
185185
./security/apparmor.nix
186-
./security/apparmor-suid.nix
187186
./security/audit.nix
188187
./security/auditd.nix
189188
./security/ca.nix

nixos/modules/security/apparmor-suid.nix

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 190 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,198 @@
11
{ config, lib, pkgs, ... }:
22

33
let
4-
inherit (lib) mkIf mkOption types concatMapStrings;
4+
inherit (builtins) attrNames head map match readFile;
5+
inherit (lib) types;
6+
inherit (config.environment) etc;
57
cfg = config.security.apparmor;
8+
mkDisableOption = name: lib.mkEnableOption name // {
9+
default = true;
10+
example = false;
11+
};
12+
enabledPolicies = lib.filterAttrs (n: p: p.enable) cfg.policies;
613
in
714

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

0 commit comments

Comments
 (0)