{ config, lib, pkgs, ... }: let inherit (builtins) attrNames head match readFile; inherit (lib) types; inherit (config.environment) etc; cfg = config.security.apparmor; in { options = { security.apparmor = { enable = lib.mkEnableOption "Enable the AppArmor Mandatory Access Control system."; 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. ''; }; }; }; config = lib.mkIf cfg.enable { environment.systemPackages = [ pkgs.apparmor-utils ]; environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" ( lib.mapAttrsToList (name: path: {inherit name path;}) cfg.profiles ); environment.etc."apparmor/parser.conf".text = 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](.*)" (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)} ${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" ]; before = [ "sysinit.target" ]; wantedBy = [ "multi-user.target" ]; unitConfig = { DefaultDependencies = "no"; }; serviceConfig = let includes = lib.concatMapStringsSep " " (p: "-I ${p}") cfg.includes; in { Type = "oneshot"; RemainAfterExit = "yes"; ExecStart = map (p: ''${pkgs.apparmor-parser}/bin/apparmor_parser -rKv ${includes} "${cfg.profiles."${p}"}"'') cfg.enforceProfiles ++ map (p: ''${pkgs.apparmor-parser}/bin/apparmor_parser -rKvC ${includes} "${cfg.profiles."${p}"}"'') cfg.complainProfiles; ExecStop = map (p: ''${pkgs.apparmor-parser}/bin/apparmor_parser -Rv ${includes} "${cfg.profiles."${p}"}"'') cfg.enforceProfiles ++ map (p: ''${pkgs.apparmor-parser}/bin/apparmor_parser -RvC ${includes} "${cfg.profiles."${p}"}"'') cfg.complainProfiles; ExecReload = map (p: ''${pkgs.apparmor-parser}/bin/apparmor_parser --reload ${includes} "${cfg.profiles."${p}"}"'') cfg.enforceProfiles ++ map (p: ''${pkgs.apparmor-parser}/bin/apparmor_parser --reload -C ${includes} "${cfg.profiles."${p}"}"'') cfg.complainProfiles; }; }; }; meta.maintainers = with lib.maintainers; [ julm ]; }