{ config, lib, pkgs, ... }: let inherit (builtins) attrNames head match readFile; inherit (lib) types; inherit (config.environment) etc; cfg = config.security.apparmor; aa-teardown = pkgs.writeShellScriptBin "aa-teardown" '' SECURITYFS=/sys/kernel/security SFS_MOUNTPOINT="$SECURITYFS/apparmor" ${pkgs.gnused}/bin/sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | \ LC_COLLATE=C sort | ${pkgs.gnugrep}/bin/grep -v // | { while read -r profile ; do printf "%s" "$profile" > "$SFS_MOUNTPOINT/.remove" rc=$? if [ "$rc" -ne 0 ] ; then retval=$rc fi done exit "''${retval:-0}" } ''; in { options = { security.apparmor = { enable = lib.mkEnableOption "Whether to enable the AppArmor Mandatory Access Control system."; enableCache = lib.mkOption { type = types.bool; default = true; example = false; description = '' Whether to enable caching of AppArmor profiles in /var/cache/apparmor/. ''; }; profiles = lib.mkOption { type = types.attrsOf types.lines; default = {}; description = '' Available AppArmor profiles. ''; apply = lib.mapAttrs (name: text: pkgs.writeText "${name}" text); }; enforceProfiles = lib.mkOption { type = (types.listOf (types.enum (attrNames cfg.profiles))) // { description = "list of profiles"; }; default = []; description = "List of AppArmor profiles to be enforced."; }; complainProfiles = lib.mkOption { type = (types.listOf (types.enum (attrNames cfg.profiles))) // { description = "list of profiles"; }; default = []; description = "List of AppArmor profiles to be complained."; }; includes = lib.mkOption { type = types.listOf types.path; default = []; description = '' List of paths to be added to AppArmor's searched paths when resolving absolute #include directives. ''; # Prepends /etc/apparmor.d so that apparmor.profiles # are included in priority over apparmor.includes. apply = is: ["/etc/apparmor.d"] ++ is; }; }; }; config = lib.mkIf cfg.enable { environment.systemPackages = [ pkgs.apparmor-utils aa-teardown ]; environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" ( lib.mapAttrsToList (name: path: {inherit name path;}) cfg.profiles ); environment.etc."apparmor/parser.conf".text = '' ${if cfg.enableCache then "write-cache" else "skip-cache"} cache-loc /var/cache/apparmor '' + lib.concatMapStringsSep "\n" (p: "Include ${p}") cfg.includes; environment.etc."apparmor/logprof.conf".text = '' [settings] profiledir = /etc/apparmor.d /etc/subdomain.d inactive_profiledir = ${pkgs.apparmor-profiles}/share/apparmor/extra-profiles logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages parser = ${pkgs.apparmor-parser}/bin/apparmor_parser ${pkgs.apparmor-parser}/bin/subdomain_parser ldd = ${pkgs.glibc.bin}/bin/ldd logger = ${pkgs.utillinux}/bin/logger # customize how file ownership permissions are presented # 0 - off # 1 - default of what ever mode the log reported # 2 - force the new permissions to be user # 3 - force all perms on the rule to be user default_owner_prompt = 1 # custom directory locations to look for #includes # # each name should be a valid directory containing possible #include # candidate files under the profile dir which by default is /etc/apparmor.d. # # So an entry of my-includes will allow /etc/apparmor.d/my-includes to # be used by the yast UI and profiling tools as a source of #include # files. custom_includes = [qualifiers] ${pkgs.runtimeShell} = icnu '' + head (match "^.*\\[qualifiers](.*)" # Drop the original [settings] section. (readFile "${pkgs.apparmor-utils}/etc/apparmor/logprof.conf")); security.apparmor.profiles = { "abstractions/tunables/alias" = '' alias /bin -> /run/current-system/sw/bin, #alias /etc -> /run/current-system/etc, alias /lib/modules -> /run/current-system/kernel/lib/modules, alias /sbin -> /run/current-system/sw/sbin, alias /usr -> /run/current-system/sw, ''; "abstractions/base" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/base ${etc."hosts".source} r, /etc/ld-nix.so.preload r, ${etc."ld-nix.so.preload".source} r, ${lib.concatMapStrings (p: lib.optionalString (p != "") (p+" mr,\n")) (lib.splitString "\n" etc."ld-nix.so.preload".text) # TODO: export the content of ld-nix.so.preload as a list or attrset # and make services.config.malloc use it. } ${pkgs.tzdata}/share/zoneinfo/** r, ''; "abstractions/consoles" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/consoles ''; "abstractions/ldapclient" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/ldapclient ''; "abstractions/kerberosclient" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/kerberosclient ''; "abstractions/likewise" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/likewise ''; "abstractions/mdns" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/mdns ''; "abstractions/nameservice" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nameservice ''; "abstractions/nis" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nis ''; "abstractions/ssl_certs" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/ssl_certs ${etc."ssl/certs/ca-certificates.crt".source} r, ${etc."ssl/certs/ca-bundle.crt".source} r, ''; "abstractions/winbind" = '' #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/winbind ''; }; security.apparmor.includes = [ (pkgs.apparmor-profiles+"/etc/apparmor.d/") ]; boot.kernelParams = [ "apparmor=1" "security=apparmor" ]; systemd.services.apparmor = { after = [ "local-fs.target" "systemd-journald-audit.socket" ]; before = [ "sysinit.target" ]; wantedBy = [ "multi-user.target" ]; restartTriggers = [ etc."apparmor/parser.conf".source etc."apparmor.d".source ] ++ cfg.includes; unitConfig = { Description="Load AppArmor profiles"; DefaultDependencies = "no"; ConditionSecurity = "apparmor"; }; environment.LANG="C"; # Avoid searchs in /usr/share/locale/ # Restart because reloading would not remove confinement from removed profiles. # However, restarting apparmor.service will restart services # which Requires= apparmor.service. restartIfChanged = true; serviceConfig = let commonOpts = p: ''--verbose --show-cache ${cfg.profiles."${p}"}''; in { Type = "oneshot"; RemainAfterExit = "yes"; ExecStartPre = "${aa-teardown}/bin/aa-teardown"; ExecStart = map (p: ''${pkgs.strace}/bin/strace -f -e file ${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}'') cfg.enforceProfiles ++ map (p: ''${pkgs.strace}/bin/strace -f -e file ${pkgs.apparmor-parser}/bin/apparmor_parser --add --complain ${commonOpts p}'') cfg.complainProfiles; ExecReload = # WARNING: reloading does NOT remove AppArmor confinement from running processes # (as we would not be able to re-confine them without restarting them). # However, processes confined with profiles just removed # from cfg.enforceProfiles or cfg.complainProfiles, # will stay confined with those removed profiles. map (p: ''${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}'') cfg.enforceProfiles ++ map (p: ''${pkgs.apparmor-parser}/bin/apparmor_parser --replace --complain ${commonOpts p}'') cfg.complainProfiles; ExecStop = "${aa-teardown}/bin/aa-teardown"; } // lib.optionalAttrs cfg.enableCache { #WorkingDirectory = "/var/cache/apparmor"; CacheDirectory = [ "apparmor" ]; CacheDirectoryMode = "0700"; }; }; }; meta.maintainers = with lib.maintainers; [ julm ]; }