1 diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix
 
   2 index 6270ac778ae..57f3dda64cd 100644
 
   3 --- a/maintainers/maintainer-list.nix
 
   4 +++ b/maintainers/maintainer-list.nix
 
   9 -    email = "julm+nix@sourcephile.fr";
 
  10 +    email = "julm+nixpkgs@sourcephile.fr";
 
  13      name = "Julien Moutinho";
 
  14 diff --git a/nixos/doc/manual/release-notes/rl-2105.xml b/nixos/doc/manual/release-notes/rl-2105.xml
 
  15 index b7947293c01..8abee64734d 100644
 
  16 --- a/nixos/doc/manual/release-notes/rl-2105.xml
 
  17 +++ b/nixos/doc/manual/release-notes/rl-2105.xml
 
  18 @@ -795,6 +795,23 @@ environment.systemPackages = [
 
  19       The option's description was incorrect regarding ownership management and has been simplified greatly.
 
  24 +     The <literal>security.apparmor</literal> module,
 
  25 +     for the <link xlink:href="https://gitlab.com/apparmor/apparmor/-/wikis/Documentation">AppArmor</link>
 
  26 +     Mandatory Access Control system,
 
  27 +     has been substantialy improved along with related tools,
 
  28 +     so that module maintainers can now more easily write AppArmor profiles for NixOS.
 
  29 +     The most notable change on the user-side is the new option <xref linkend="opt-security.apparmor.policies"/>,
 
  30 +     replacing the previous <literal>profiles</literal> option
 
  31 +     to provide a way to disable a profile
 
  32 +     and to select whether to confine in enforce mode (default)
 
  33 +     or in complain mode (see <literal>journalctl -b --grep apparmor</literal>).
 
  34 +     Security-minded users may also want to enable <xref linkend="opt-security.apparmor.killUnconfinedConfinables"/>,
 
  35 +     at the cost of having some of their processes killed
 
  36 +     when updating to a NixOS version introducing new AppArmor profiles.
 
  41         The GNOME desktop manager once again installs <package>gnome3.epiphany</package> by default.
 
  42 diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
 
  43 index 6e7b8c4b88a..72827c5abaa 100644
 
  44 --- a/nixos/modules/config/fonts/fontconfig.nix
 
  45 +++ b/nixos/modules/config/fonts/fontconfig.nix
 
  46 @@ -448,6 +448,40 @@ in
 
  48        environment.systemPackages    = [ pkgs.fontconfig ];
 
  49        environment.etc.fonts.source  = "${fontconfigEtc}/etc/fonts/";
 
  50 +      security.apparmor.includes."abstractions/fonts" = ''
 
  52 +        r ${pkg.out}/etc/fonts/fonts.conf,
 
  54 +        # fontconfig default config files
 
  55 +        r ${pkg.out}/etc/fonts/conf.d/*.conf,
 
  57 +        # 00-nixos-cache.conf
 
  60 +        # 10-nixos-rendering.conf
 
  64 +        ${optionalString cfg.includeUserConf ''
 
  65 +        r ${pkg.out}/etc/fonts/conf.d.bak/50-user.conf,
 
  68 +        # local.conf (indirect priority 51)
 
  69 +        ${optionalString (cfg.localConf != "") ''
 
  73 +        # 52-nixos-default-fonts.conf
 
  74 +        r ${defaultFontsConf},
 
  76 +        # 53-no-bitmaps.conf
 
  79 +        ${optionalString (!cfg.allowType1) ''
 
  80 +        # 53-nixos-reject-type1.conf
 
  86        fonts.fontconfig.confPackages = [ confPkg ];
 
  87 diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
 
  88 index a3eb55d8a42..fc35993b5a8 100644
 
  89 --- a/nixos/modules/config/malloc.nix
 
  90 +++ b/nixos/modules/config/malloc.nix
 
  92      environment.etc."ld-nix.so.preload".text = ''
 
  95 +    security.apparmor.includes = {
 
  96 +      "abstractions/base" = ''
 
  97 +        r /etc/ld-nix.so.preload,
 
  98 +        r ${config.environment.etc."ld-nix.so.preload".source},
 
  99 +        mr ${providerLibPath},
 
 104 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
 
 105 index 4a63a09ab84..43f052d150e 100644
 
 106 --- a/nixos/modules/module-list.nix
 
 107 +++ b/nixos/modules/module-list.nix
 
 111    ./security/apparmor.nix
 
 112 -  ./security/apparmor-suid.nix
 
 114    ./security/auditd.nix
 
 116 diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
 
 117 index 00aafc6831b..3f8f78f012a 100644
 
 118 --- a/nixos/modules/profiles/hardened.nix
 
 119 +++ b/nixos/modules/profiles/hardened.nix
 
 120 @@ -36,6 +36,7 @@ with lib;
 
 121    security.virtualisation.flushL1DataCache = mkDefault "always";
 
 123    security.apparmor.enable = mkDefault true;
 
 124 +  security.apparmor.killUnconfinedConfinables = mkDefault true;
 
 126    boot.kernelParams = [
 
 127      # Slab/slub sanity checks, redzoning, and poisoning
 
 128 diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix
 
 129 deleted file mode 100644
 
 130 index 6c479e070e2..00000000000
 
 131 --- a/nixos/modules/security/apparmor-suid.nix
 
 134 -{ config, lib, pkgs, ... }:
 
 136 -  cfg = config.security.apparmor;
 
 141 -    (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ])
 
 144 -  options.security.apparmor.confineSUIDApplications = mkOption {
 
 148 -      Install AppArmor profiles for commonly-used SUID application
 
 149 -      to mitigate potential privilege escalation attacks due to bugs
 
 150 -      in such applications.
 
 152 -      Currently available profiles: ping
 
 156 -  config = mkIf (cfg.confineSUIDApplications) {
 
 157 -    security.apparmor.profiles = [ (pkgs.writeText "ping" ''
 
 158 -      #include <tunables/global>
 
 159 -      /run/wrappers/bin/ping {
 
 160 -        #include <abstractions/base>
 
 161 -        #include <abstractions/consoles>
 
 162 -        #include <abstractions/nameservice>
 
 164 -        capability net_raw,
 
 168 -        ${pkgs.stdenv.cc.libc.out}/lib/*.so mr,
 
 169 -        ${pkgs.libcap.lib}/lib/libcap.so* mr,
 
 170 -        ${pkgs.attr.out}/lib/libattr.so* mr,
 
 172 -        ${pkgs.iputils}/bin/ping mixr,
 
 174 -        #/etc/modules.conf r,
 
 176 -        ## Site-specific additions and overrides. See local/README for details.
 
 177 -        ##include <local/bin.ping>
 
 183 diff --git a/nixos/modules/security/apparmor.nix b/nixos/modules/security/apparmor.nix
 
 184 index cfc65b347bc..8683d2b487c 100644
 
 185 --- a/nixos/modules/security/apparmor.nix
 
 186 +++ b/nixos/modules/security/apparmor.nix
 
 188  { config, lib, pkgs, ... }:
 
 193 -  inherit (lib) mkIf mkOption types concatMapStrings;
 
 194 +  inherit (builtins) attrNames head map match readFile;
 
 195 +  inherit (lib) types;
 
 196 +  inherit (config.environment) etc;
 
 197    cfg = config.security.apparmor;
 
 198 +  mkDisableOption = name: mkEnableOption name // {
 
 202 +  enabledPolicies = filterAttrs (n: p: p.enable) cfg.policies;
 
 207 -     security.apparmor = {
 
 208 -       enable = mkOption {
 
 211 -         description = "Enable the AppArmor Mandatory Access Control system.";
 
 213 -       profiles = mkOption {
 
 214 -         type = types.listOf types.path;
 
 216 -         description = "List of files containing AppArmor profiles.";
 
 218 -       packages = mkOption {
 
 219 -         type = types.listOf types.package;
 
 221 -         description = "List of packages to be added to apparmor's include path";
 
 226 +    (mkRemovedOptionModule [ "security" "apparmor" "confineSUIDApplications" ] "Please use the new options: `security.apparmor.policies.<policy>.enable'.")
 
 227 +    (mkRemovedOptionModule [ "security" "apparmor" "profiles" ] "Please use the new option: `security.apparmor.policies'.")
 
 228 +    apparmor/includes.nix
 
 229 +    apparmor/profiles.nix
 
 232 -   config = mkIf cfg.enable {
 
 233 -     environment.systemPackages = [ pkgs.apparmor-utils ];
 
 235 +    security.apparmor = {
 
 236 +      enable = mkEnableOption ''
 
 237 +        the AppArmor Mandatory Access Control system.
 
 239 -     boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
 
 240 +        If you're enabling this module on a running system,
 
 241 +        note that a reboot will be required to activate AppArmor in the kernel.
 
 243 -     systemd.services.apparmor = let
 
 244 -       paths = concatMapStrings (s: " -I ${s}/etc/apparmor.d")
 
 245 -         ([ pkgs.apparmor-profiles ] ++ cfg.packages);
 
 247 -       after = [ "local-fs.target" ];
 
 248 -       before = [ "sysinit.target" ];
 
 249 -       wantedBy = [ "multi-user.target" ];
 
 251 -         DefaultDependencies = "no";
 
 255 -         RemainAfterExit = "yes";
 
 256 -         ExecStart = map (p:
 
 257 -           ''${pkgs.apparmor-parser}/bin/apparmor_parser -rKv ${paths} "${p}"''
 
 260 -           ''${pkgs.apparmor-parser}/bin/apparmor_parser -Rv "${p}"''
 
 262 -         ExecReload = map (p:
 
 263 -           ''${pkgs.apparmor-parser}/bin/apparmor_parser --reload ${paths} "${p}"''
 
 268 +        Also, beware that enabling this module privileges stability over security
 
 269 +        by not trying to kill unconfined but newly confinable running processes by default,
 
 270 +        which can happen because AppArmor can only confine new
 
 271 +        or already confined processes of an executable.
 
 272 +        This will happen when upgrading to a NixOS revision
 
 273 +        introducing an AppArmor profile for the executable of a running process.
 
 275 +        Enable <xref linkend="opt-security.apparmor.killUnconfinedConfinables"/>
 
 276 +        if you want this service to send a </literal>SIGTERM</literal> to those running processes'';
 
 277 +      policies = mkOption {
 
 281 +        type = types.attrsOf (types.submodule ({ name, config, ... }: {
 
 283 +            enable = mkDisableOption "loading of the profile into the kernel";
 
 284 +            enforce = mkDisableOption "enforcing of the policy or only complain in the logs";
 
 285 +            profile = mkOption {
 
 286 +              description = "The policy of the profile.";
 
 287 +              type = types.lines;
 
 288 +              apply = pkgs.writeText name;
 
 294 +      includes = mkOption {
 
 295 +        type = types.attrsOf types.lines;
 
 298 +          List of paths to be added to AppArmor's searched paths
 
 299 +          when resolving <literal>include</literal> directives.
 
 301 +        apply = mapAttrs pkgs.writeText;
 
 303 +      packages = mkOption {
 
 304 +        type = types.listOf types.package;
 
 306 +        description = "List of packages to be added to AppArmor's include path";
 
 308 +      enableCache = mkEnableOption ''
 
 309 +        caching of AppArmor policies
 
 310 +        in <literal>/var/cache/apparmor/</literal>.
 
 312 +        Beware that AppArmor policies almost always contain Nix store paths,
 
 313 +        and thus produce at each change of these paths
 
 314 +        a new cached version accumulating in the cache'';
 
 315 +      killUnconfinedConfinables = mkEnableOption ''
 
 316 +        killing of processes which have an AppArmor profile enabled
 
 317 +        (in <xref linkend="opt-security.apparmor.policies"/>)
 
 318 +        but are not confined (because AppArmor can only confine new processes).
 
 320 +        This is only sending a gracious <literal>SIGTERM</literal> signal to the processes,
 
 321 +        not a <literal>SIGKILL</literal>.
 
 323 +        Beware that due to a current limitation of AppArmor,
 
 324 +        only profiles with exact paths (and no name) can enable such kills'';
 
 328 +  config = mkIf cfg.enable {
 
 329 +    assertions = map (policy:
 
 330 +      { assertion = match ".*/.*" policy == null;
 
 331 +        message = "`security.apparmor.policies.\"${policy}\"' must not contain a slash.";
 
 332 +        # Because, for instance, aa-remove-unknown uses profiles_names_list() in rc.apparmor.functions
 
 333 +        # which does not recurse into sub-directories.
 
 335 +    ) (attrNames cfg.policies);
 
 337 +    environment.systemPackages = [
 
 338 +      pkgs.apparmor-utils
 
 339 +      pkgs.apparmor-bin-utils
 
 341 +    environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" (
 
 342 +      # It's important to put only enabledPolicies here and not all cfg.policies
 
 343 +      # because aa-remove-unknown reads profiles from all /etc/apparmor.d/*
 
 344 +      mapAttrsToList (name: p: { inherit name; path = p.profile; }) enabledPolicies ++
 
 345 +      mapAttrsToList (name: path: { inherit name path; }) cfg.includes
 
 347 +    environment.etc."apparmor/parser.conf".text = ''
 
 348 +        ${if cfg.enableCache then "write-cache" else "skip-cache"}
 
 349 +        cache-loc /var/cache/apparmor
 
 350 +        Include /etc/apparmor.d
 
 352 +      concatMapStrings (p: "Include ${p}/etc/apparmor.d\n") cfg.packages;
 
 354 +    environment.etc."apparmor/apparmor.conf".text = ''
 
 357 +    environment.etc."apparmor/severity.db".source = pkgs.apparmor-utils + "/etc/apparmor/severity.db";
 
 358 +    environment.etc."apparmor/logprof.conf".source = pkgs.runCommand "logprof.conf" {
 
 361 +          # /etc/apparmor.d/ is read-only on NixOS
 
 362 +          profiledir = /var/cache/apparmor/logprof
 
 363 +          inactive_profiledir = /etc/apparmor.d/disable
 
 364 +          # Use: journalctl -b --since today --grep audit: | aa-logprof
 
 365 +          logfiles = /dev/stdin
 
 367 +          parser = ${pkgs.apparmor-parser}/bin/apparmor_parser
 
 368 +          ldd = ${pkgs.glibc.bin}/bin/ldd
 
 369 +          logger = ${pkgs.utillinux}/bin/logger
 
 371 +          # customize how file ownership permissions are presented
 
 373 +          # 1 - default of what ever mode the log reported
 
 374 +          # 2 - force the new permissions to be user
 
 375 +          # 3 - force all perms on the rule to be user
 
 376 +          default_owner_prompt = 1
 
 378 +          custom_includes = /etc/apparmor.d ${concatMapStringsSep " " (p: "${p}/etc/apparmor.d") cfg.packages}
 
 381 +          ${pkgs.runtimeShell} = icnu
 
 382 +          ${pkgs.bashInteractive}/bin/sh = icnu
 
 383 +          ${pkgs.bashInteractive}/bin/bash = icnu
 
 384 +          ${config.users.defaultUserShell} = icnu
 
 386 +      footer = "${pkgs.apparmor-utils}/etc/apparmor/logprof.conf";
 
 387 +      passAsFile = [ "header" ];
 
 389 +      cp $headerPath $out
 
 390 +      sed '1,/\[qualifiers\]/d' $footer >> $out
 
 393 +    boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
 
 395 +    systemd.services.apparmor = {
 
 398 +        "systemd-journald-audit.socket"
 
 400 +      before = [ "sysinit.target" ];
 
 401 +      wantedBy = [ "multi-user.target" ];
 
 403 +        Description="Load AppArmor policies";
 
 404 +        DefaultDependencies = "no";
 
 405 +        ConditionSecurity = "apparmor";
 
 407 +      # Reloading instead of restarting enables to load new AppArmor profiles
 
 408 +      # without necessarily restarting all services which have Requires=apparmor.service
 
 409 +      reloadIfChanged = true;
 
 410 +      restartTriggers = [
 
 411 +        etc."apparmor/parser.conf".source
 
 412 +        etc."apparmor.d".source
 
 414 +      serviceConfig = let
 
 415 +        killUnconfinedConfinables = pkgs.writeShellScript "apparmor-kill" ''
 
 417 +          ${pkgs.apparmor-bin-utils}/bin/aa-status --json |
 
 418 +          ${pkgs.jq}/bin/jq --raw-output '.processes | .[] | .[] | select (.status == "unconfined") | .pid' |
 
 419 +          xargs --verbose --no-run-if-empty --delimiter='\n' \
 
 422 +        commonOpts = p: "--verbose --show-cache ${optionalString (!p.enforce) "--complain "}${p.profile}";
 
 425 +        RemainAfterExit = "yes";
 
 426 +        ExecStartPre = "${pkgs.apparmor-utils}/bin/aa-teardown";
 
 427 +        ExecStart = mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}") enabledPolicies;
 
 428 +        ExecStartPost = optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
 
 430 +          # Add or replace into the kernel profiles in enabledPolicies
 
 431 +          # (because AppArmor can do that without stopping the processes already confined).
 
 432 +          mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}") enabledPolicies ++
 
 433 +          # Remove from the kernel any profile whose name is not
 
 434 +          # one of the names within the content of the profiles in enabledPolicies
 
 435 +          # (indirectly read from /etc/apparmor.d/*, without recursing into sub-directory).
 
 436 +          # Note that this does not remove profiles dynamically generated by libvirt.
 
 437 +          [ "${pkgs.apparmor-utils}/bin/aa-remove-unknown" ] ++
 
 438 +          # Optionaly kill the processes which are unconfined but now have a profile loaded
 
 439 +          # (because AppArmor can only start to confine new processes).
 
 440 +          optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
 
 441 +        ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown";
 
 442 +        CacheDirectory = [ "apparmor" "apparmor/logprof" ];
 
 443 +        CacheDirectoryMode = "0700";
 
 448 +  meta.maintainers = with maintainers; [ julm ];
 
 450 diff --git a/nixos/modules/security/apparmor/includes.nix b/nixos/modules/security/apparmor/includes.nix
 
 452 index 00000000000..e3dd410b3bb
 
 454 +++ b/nixos/modules/security/apparmor/includes.nix
 
 456 +{ config, lib, pkgs, ... }:
 
 458 +  inherit (builtins) attrNames hasAttr isAttrs;
 
 459 +  inherit (lib) getLib;
 
 460 +  inherit (config.environment) etc;
 
 461 +  # Utility to generate an AppArmor rule
 
 462 +  # only when the given path exists in config.environment.etc
 
 464 +    let go = { path ? null, mode ? "r", trail ? "" }:
 
 465 +      lib.optionalString (hasAttr path etc)
 
 466 +        "${mode} ${config.environment.etc.${path}.source}${trail},";
 
 469 +    else go { path = arg; };
 
 472 +# FIXME: most of the etcRule calls below have been
 
 473 +# written systematically by converting from apparmor-profiles's profiles
 
 474 +# without testing nor deep understanding of their uses,
 
 475 +# and thus may need more rules or can have less rules;
 
 476 +# this remains to be determined case by case,
 
 477 +# some may even be completely useless.
 
 478 +config.security.apparmor.includes = {
 
 479 +  # This one is included by <tunables/global>
 
 480 +  # which is usualy included before any profile.
 
 481 +  "abstractions/tunables/alias" = ''
 
 482 +    alias /bin -> /run/current-system/sw/bin,
 
 483 +    alias /lib/modules -> /run/current-system/kernel/lib/modules,
 
 484 +    alias /sbin -> /run/current-system/sw/sbin,
 
 485 +    alias /usr -> /run/current-system/sw,
 
 487 +  "abstractions/audio" = ''
 
 488 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/audio"
 
 489 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 493 +      { path = "pulse";  trail = "/"; }
 
 494 +      { path = "pulse";  trail = "/**"; }
 
 495 +      { path = "sound";  trail = "/"; }
 
 496 +      { path = "sound";  trail = "/**"; }
 
 497 +      { path = "alsa/conf.d";  trail = "/"; }
 
 498 +      { path = "alsa/conf.d";  trail = "/*"; }
 
 499 +      "openal/alsoft.conf"
 
 500 +      "wildmidi/wildmidi.conf"
 
 502 +  "abstractions/authentication" = ''
 
 503 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/authentication"
 
 504 +    # Defined in security.pam
 
 505 +    include <abstractions/pam>
 
 506 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 509 +      { path = "security";  trail = "/*"; }
 
 516 +  "abstractions/base" = ''
 
 517 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/base"
 
 518 +    r ${pkgs.stdenv.cc.libc}/share/locale/**,
 
 519 +    r ${pkgs.stdenv.cc.libc}/share/locale.alias,
 
 520 +    ${lib.optionalString (pkgs.glibcLocales != null) "r ${pkgs.glibcLocales}/lib/locale/locale-archive,"}
 
 521 +    ${etcRule "localtime"}
 
 522 +    r ${pkgs.tzdata}/share/zoneinfo/**,
 
 523 +    r ${pkgs.stdenv.cc.libc}/share/i18n/**,
 
 525 +  "abstractions/bash" = ''
 
 526 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/bash"
 
 528 +    # bash inspects filesystems at startup
 
 529 +    # and /etc/mtab is linked to /proc/mounts
 
 532 +    # system-wide bash configuration
 
 533 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 537 +      { path = "profile.d";  trail = "/*"; }
 
 540 +      "bash.bashrc.local"
 
 542 +      "bash_completion.d"
 
 543 +      { path = "bash_completion.d";  trail = "/*"; }
 
 544 +      # bash relies on system-wide readline configuration
 
 546 +      # run out of /etc/bash.bashrc
 
 549 +  "abstractions/consoles" = ''
 
 550 +     include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/consoles"
 
 552 +  "abstractions/cups-client" = ''
 
 553 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/cpus-client"
 
 554 +    ${etcRule "cups/cups-client.conf"}
 
 556 +  "abstractions/dbus-session-strict" = ''
 
 557 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dbus-session-strict"
 
 558 +    ${etcRule "machine-id"}
 
 560 +  "abstractions/dconf" = ''
 
 561 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dconf"
 
 562 +    ${etcRule { path = "dconf";  trail = "/**"; }}
 
 564 +  "abstractions/dri-common" = ''
 
 565 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dri-common"
 
 568 +  # The config.fonts.fontconfig NixOS module adds many files to /etc/fonts/
 
 569 +  # by symlinking them but without exporting them outside of its NixOS module,
 
 570 +  # those are therefore added there to this "abstractions/fonts".
 
 571 +  "abstractions/fonts" = ''
 
 572 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/fonts"
 
 573 +    ${etcRule { path = "fonts";  trail = "/**"; }}
 
 575 +  "abstractions/gnome" = ''
 
 576 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/gnome"
 
 577 +    include <abstractions/fonts>
 
 578 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 579 +      { path = "gnome";  trail = "/gtkrc*"; }
 
 580 +      { path = "gtk";  trail = "/*"; }
 
 581 +      { path = "gtk-2.0";  trail = "/*"; }
 
 582 +      { path = "gtk-3.0";  trail = "/*"; }
 
 584 +      { path = "pango";  trail = "/*"; }
 
 585 +      { path = "/etc/gnome-vfs-2.0";  trail = "/modules/"; }
 
 586 +      { path = "/etc/gnome-vfs-2.0";  trail = "/modules/*"; }
 
 588 +      { path = "cups";  trail = "/lpoptions"; }
 
 589 +      { path = "gnome";  trail = "/defaults.list"; }
 
 590 +      { path = "xdg";  trail = "/{,*-}mimeapps.list"; }
 
 591 +      "xdg/mimeapps.list"
 
 593 +  "abstractions/kde" = ''
 
 594 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/kde"
 
 595 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 596 +      { path = "qt3";  trail = "/kstylerc"; }
 
 597 +      { path = "qt3";  trail = "/qt_plugins_3.3rc"; }
 
 598 +      { path = "qt3";  trail = "/qtrc"; }
 
 600 +      { path = "kde3";  trail = "/*"; }
 
 602 +      { path = "xdg";  trail = "/kdeglobals"; }
 
 603 +      { path = "xdg";  trail = "/Trolltech.conf"; }
 
 605 +  "abstractions/kerberosclient" = ''
 
 606 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/kerberosclient"
 
 607 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 608 +    { path = "krb5.keytab"; mode="rk"; }
 
 611 +    { path = "krb5.conf.d";  trail = "/*"; }
 
 613 +    # config files found via strings on libs
 
 618 +  "abstractions/ldapclient" = ''
 
 619 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/ldapclient"
 
 620 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 623 +      { path = "openldap";  trail = "/*"; }
 
 624 +      { path = "openldap";  trail = "/cacerts/*"; }
 
 625 +      { path = "sasl2";  trail = "/*"; }
 
 627 +  "abstractions/likewise" = ''
 
 628 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/likewise"
 
 630 +  "abstractions/mdns" = ''
 
 631 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/mdns"
 
 632 +    ${etcRule "nss_mdns.conf"}
 
 634 +  "abstractions/nameservice" = ''
 
 635 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nameservice"
 
 637 +    # Many programs wish to perform nameservice-like operations, such as
 
 638 +    # looking up users by name or id, groups by name or id, hosts by name
 
 639 +    # or IP, etc. These operations may be performed through files, dns,
 
 640 +    # NIS, NIS+, LDAP, hesiod, wins, etc. Allow them all here.
 
 641 +    mr ${getLib pkgs.nss}/lib/libnss_*.so*,
 
 642 +    mr ${getLib pkgs.nss}/lib64/libnss_*.so*,
 
 643 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 652 +      # libtirpc (used for NIS/YP login) needs this
 
 657 +      { path = "samba";  trail = "/lmhosts"; }
 
 662 +      # libnl-3-200 via libnss-gw-name
 
 663 +      { path = "libnl";  trail = "/classid"; }
 
 664 +      { path = "libnl-3";  trail = "/classid"; }
 
 666 +  "abstractions/nis" = ''
 
 667 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nis"
 
 669 +  "abstractions/nvidia" = ''
 
 670 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nvidia"
 
 671 +    ${etcRule "vdpau_wrapper.cfg"}
 
 673 +  "abstractions/opencl-common" = ''
 
 674 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/opencl-common"
 
 675 +    ${etcRule { path = "OpenCL";  trail = "/**"; }}
 
 677 +  "abstractions/opencl-mesa" = ''
 
 678 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/opencl-mesa"
 
 679 +    ${etcRule "default/drirc"}
 
 681 +  "abstractions/openssl" = ''
 
 682 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/openssl"
 
 683 +    ${etcRule { path = "ssl";  trail = "/openssl.cnf"; }}
 
 685 +  "abstractions/p11-kit" = ''
 
 686 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/p11-kit"
 
 687 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 688 +      { path = "pkcs11";  trail = "/"; }
 
 689 +      { path = "pkcs11";  trail = "/pkcs11.conf"; }
 
 690 +      { path = "pkcs11";  trail = "/modules/"; }
 
 691 +      { path = "pkcs11";  trail = "/modules/*"; }
 
 693 +  "abstractions/perl" = ''
 
 694 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/perl"
 
 695 +    ${etcRule { path = "perl";  trail = "/**"; }}
 
 697 +  "abstractions/php" = ''
 
 698 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/php"
 
 699 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 700 +      { path = "php";  trail = "/**/"; }
 
 701 +      { path = "php5";  trail = "/**/"; }
 
 702 +      { path = "php7";  trail = "/**/"; }
 
 703 +      { path = "php";  trail = "/**.ini"; }
 
 704 +      { path = "php5";  trail = "/**.ini"; }
 
 705 +      { path = "php7";  trail = "/**.ini"; }
 
 707 +  "abstractions/postfix-common" = ''
 
 708 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/postfix-common"
 
 709 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 711 +      { path = "postfix";  trail = "/*.cf"; }
 
 713 +      "postfix/master.cf"
 
 715 +  "abstractions/python" = ''
 
 716 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/python"
 
 718 +  "abstractions/qt5" = ''
 
 719 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/qt5"
 
 720 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 721 +      { path = "xdg";  trail = "/QtProject/qtlogging.ini"; }
 
 722 +      { path = "xdg/QtProject";  trail = "/qtlogging.ini"; }
 
 723 +      "xdg/QtProject/qtlogging.ini"
 
 725 +  "abstractions/samba" = ''
 
 726 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/samba"
 
 727 +    ${etcRule { path = "samba";  trail = "/*"; }}
 
 729 +  "abstractions/ssl_certs" = ''
 
 730 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/ssl_certs"
 
 732 +    # For the NixOS module: security.acme
 
 733 +    r /var/lib/acme/*/cert.pem,
 
 734 +    r /var/lib/acme/*/chain.pem,
 
 735 +    r /var/lib/acme/*/fullchain.pem,
 
 737 +    '' + lib.concatMapStringsSep "\n" etcRule [
 
 738 +      "ssl/certs/ca-certificates.crt"
 
 739 +      "ssl/certs/ca-bundle.crt"
 
 740 +      "pki/tls/certs/ca-bundle.crt"
 
 742 +      { path = "ssl/trust";  trail = "/"; }
 
 743 +      { path = "ssl/trust";  trail = "/*"; }
 
 744 +      { path = "ssl/trust/anchors";  trail = "/"; }
 
 745 +      { path = "ssl/trust/anchors";  trail = "/**"; }
 
 746 +      { path = "pki/trust";  trail = "/"; }
 
 747 +      { path = "pki/trust";  trail = "/*"; }
 
 748 +      { path = "pki/trust/anchors";  trail = "/"; }
 
 749 +      { path = "pki/trust/anchors";  trail = "/**"; }
 
 751 +  "abstractions/ssl_keys" = ''
 
 752 +    # security.acme NixOS module
 
 753 +    r /var/lib/acme/*/full.pem,
 
 754 +    r /var/lib/acme/*/key.pem,
 
 756 +  "abstractions/vulkan" = ''
 
 757 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/vulkan"
 
 758 +    ${etcRule { path = "vulkan/icd.d";  trail = "/"; }}
 
 759 +    ${etcRule { path = "vulkan/icd.d";  trail = "/*.json"; }}
 
 761 +  "abstractions/winbind" = ''
 
 762 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/winbind"
 
 763 +    ${etcRule { path = "samba";  trail = "/smb.conf"; }}
 
 764 +    ${etcRule { path = "samba";  trail = "/dhcp.conf"; }}
 
 766 +  "abstractions/X" = ''
 
 767 +    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/X"
 
 768 +    ${etcRule { path = "X11/cursors";  trail = "/"; }}
 
 769 +    ${etcRule { path = "X11/cursors";  trail = "/**"; }}
 
 773 diff --git a/nixos/modules/security/apparmor/profiles.nix b/nixos/modules/security/apparmor/profiles.nix
 
 775 index 00000000000..8eb630b5a48
 
 777 +++ b/nixos/modules/security/apparmor/profiles.nix
 
 779 +{ config, lib, pkgs, ... }:
 
 780 +let apparmor = config.security.apparmor; in
 
 782 +config.security.apparmor.packages = [ pkgs.apparmor-profiles ];
 
 783 +config.security.apparmor.policies."bin.ping".profile = lib.mkIf apparmor.policies."bin.ping".enable ''
 
 784 +  include "${pkgs.iputils.apparmor}/bin.ping"
 
 785 +  include "${pkgs.inetutils.apparmor}/bin.ping"
 
 786 +  # Note that including those two profiles in the same profile
 
 787 +  # would not work if the second one were to re-include <tunables/global>.
 
 790 diff --git a/nixos/modules/security/misc.nix b/nixos/modules/security/misc.nix
 
 791 index d51dbbb77f7..e7abc1e0d59 100644
 
 792 --- a/nixos/modules/security/misc.nix
 
 793 +++ b/nixos/modules/security/misc.nix
 
 794 @@ -7,6 +7,10 @@ with lib;
 
 795      maintainers = [ maintainers.joachifm ];
 
 799 +    (lib.mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ])
 
 803      security.allowUserNamespaces = mkOption {
 
 805 diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
 
 806 index 103cf205012..1c49131d789 100644
 
 807 --- a/nixos/modules/security/pam.nix
 
 808 +++ b/nixos/modules/security/pam.nix
 
 809 @@ -895,6 +895,81 @@ in
 
 810          runuser-l = { rootOK = true; unixAuth = false; };
 
 813 +    security.apparmor.includes."abstractions/pam" = let
 
 814 +      isEnabled = test: fold or false (map test (attrValues config.security.pam.services));
 
 816 +      lib.concatMapStringsSep "\n"
 
 817 +        (name: "r ${config.environment.etc."pam.d/${name}".source},")
 
 818 +        (attrNames config.security.pam.services) +
 
 820 +      mr ${getLib pkgs.pam}/lib/security/pam_filter/*,
 
 821 +      mr ${getLib pkgs.pam}/lib/security/pam_*.so,
 
 822 +      r ${getLib pkgs.pam}/lib/security/,
 
 824 +      optionalString use_ldap ''
 
 825 +         mr ${pam_ldap}/lib/security/pam_ldap.so,
 
 827 +      optionalString config.services.sssd.enable ''
 
 828 +        mr ${pkgs.sssd}/lib/security/pam_sss.so,
 
 830 +      optionalString config.krb5.enable ''
 
 831 +        mr ${pam_krb5}/lib/security/pam_krb5.so,
 
 832 +        mr ${pam_ccreds}/lib/security/pam_ccreds.so,
 
 834 +      optionalString (isEnabled (cfg: cfg.googleOsLoginAccountVerification)) ''
 
 835 +        mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so,
 
 836 +        mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_admin.so,
 
 838 +      optionalString (isEnabled (cfg: cfg.googleOsLoginAuthentication)) ''
 
 839 +        mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so,
 
 841 +      optionalString (config.security.pam.enableSSHAgentAuth
 
 842 +                     && isEnabled (cfg: cfg.sshAgentAuth)) ''
 
 843 +        mr ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so,
 
 845 +      optionalString (isEnabled (cfg: cfg.fprintAuth)) ''
 
 846 +        mr ${pkgs.fprintd}/lib/security/pam_fprintd.so,
 
 848 +      optionalString (isEnabled (cfg: cfg.u2fAuth)) ''
 
 849 +        mr ${pkgs.pam_u2f}/lib/security/pam_u2f.so,
 
 851 +      optionalString (isEnabled (cfg: cfg.usbAuth)) ''
 
 852 +        mr ${pkgs.pam_usb}/lib/security/pam_usb.so,
 
 854 +      optionalString (isEnabled (cfg: cfg.oathAuth)) ''
 
 855 +        "mr ${pkgs.oathToolkit}/lib/security/pam_oath.so,
 
 857 +      optionalString (isEnabled (cfg: cfg.yubicoAuth)) ''
 
 858 +        mr ${pkgs.yubico-pam}/lib/security/pam_yubico.so,
 
 860 +      optionalString (isEnabled (cfg: cfg.duoSecurity.enable)) ''
 
 861 +        mr ${pkgs.duo-unix}/lib/security/pam_duo.so,
 
 863 +      optionalString (isEnabled (cfg: cfg.otpwAuth)) ''
 
 864 +        mr ${pkgs.otpw}/lib/security/pam_otpw.so,
 
 866 +      optionalString config.security.pam.enableEcryptfs ''
 
 867 +        mr ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so,
 
 869 +      optionalString (isEnabled (cfg: cfg.pamMount)) ''
 
 870 +        mr ${pkgs.pam_mount}/lib/security/pam_mount.so,
 
 872 +      optionalString (isEnabled (cfg: cfg.enableGnomeKeyring)) ''
 
 873 +        mr ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so,
 
 875 +      optionalString (isEnabled (cfg: cfg.startSession)) ''
 
 876 +        mr ${pkgs.systemd}/lib/security/pam_systemd.so,
 
 878 +      optionalString (isEnabled (cfg: cfg.enableAppArmor)
 
 879 +                     && config.security.apparmor.enable) ''
 
 880 +        mr ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so,
 
 882 +      optionalString (isEnabled (cfg: cfg.enableKwallet)) ''
 
 883 +        mr ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so,
 
 885 +      optionalString config.virtualisation.lxc.lxcfs.enable ''
 
 886 +        mr ${pkgs.lxc}/lib/security/pam_cgfs.so
 
 891 diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
 
 892 index 3cbf22fea7a..1e65f451515 100644
 
 893 --- a/nixos/modules/security/wrappers/default.nix
 
 894 +++ b/nixos/modules/security/wrappers/default.nix
 
 895 @@ -171,6 +171,14 @@ in
 
 896        export PATH="${wrapperDir}:$PATH"
 
 899 +    security.apparmor.includes."nixos/security.wrappers" = ''
 
 900 +      include "${pkgs.apparmorRulesFromClosure { name="security.wrappers"; } [
 
 903 +        pkgs.stdenv.cc.libc
 
 907      ###### setcap activation script
 
 908      system.activationScripts.wrappers =
 
 909        lib.stringAfter [ "specialfs" "users" ]
 
 910 diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
 
 911 index 7bec073e26f..779924a65a8 100644
 
 912 --- a/nixos/modules/services/torrent/transmission.nix
 
 913 +++ b/nixos/modules/services/torrent/transmission.nix
 
 914 @@ -5,17 +5,22 @@ with lib;
 
 916    cfg = config.services.transmission;
 
 917    inherit (config.environment) etc;
 
 918 -  apparmor = config.security.apparmor.enable;
 
 919 +  apparmor = config.security.apparmor;
 
 920    rootDir = "/run/transmission";
 
 921 -  homeDir = "/var/lib/transmission";
 
 922    settingsDir = ".config/transmission-daemon";
 
 923    downloadsDir = "Downloads";
 
 924    incompleteDir = ".incomplete";
 
 925    watchDir = "watchdir";
 
 926 -  # TODO: switch to configGen.json once RFC0042 is implemented
 
 927 -  settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
 
 928 +  settingsFormat = pkgs.formats.json {};
 
 929 +  settingsFile = settingsFormat.generate "settings.json" cfg.settings;
 
 933 +    (mkRenamedOptionModule ["services" "transmission" "port"]
 
 934 +                           ["services" "transmission" "settings" "rpc-port"])
 
 935 +    (mkAliasOptionModule ["services" "transmission" "openFirewall"]
 
 936 +                         ["services" "transmission" "openPeerPorts"])
 
 939      services.transmission = {
 
 940        enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
 
 941 @@ -24,48 +29,141 @@ in
 
 942          transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
 
 943          or other clients like stig or tremc.
 
 945 -        Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
 
 946 +        Torrents are downloaded to <xref linkend="opt-services.transmission.home"/>/${downloadsDir} by default and are
 
 947          accessible to users in the "transmission" group'';
 
 949 -      settings = mkOption rec {
 
 950 -        # TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented
 
 951 -        type = types.attrs;
 
 952 -        apply = recursiveUpdate default;
 
 955 -            download-dir = "${cfg.home}/${downloadsDir}";
 
 956 -            incomplete-dir = "${cfg.home}/${incompleteDir}";
 
 957 -            incomplete-dir-enabled = true;
 
 958 -            watch-dir = "${cfg.home}/${watchDir}";
 
 959 -            watch-dir-enabled = false;
 
 962 -            peer-port-random-high = 65535;
 
 963 -            peer-port-random-low = 49152;
 
 964 -            peer-port-random-on-start = false;
 
 965 -            rpc-bind-address = "127.0.0.1";
 
 967 -            script-torrent-done-enabled = false;
 
 968 -            script-torrent-done-filename = "";
 
 969 -            umask = 2; # 0o002 in decimal as expected by Transmission
 
 970 -            utp-enabled = true;
 
 974 -            download-dir = "/srv/torrents/";
 
 975 -            incomplete-dir = "/srv/torrents/.incomplete/";
 
 976 -            incomplete-dir-enabled = true;
 
 977 -            rpc-whitelist = "127.0.0.1,192.168.*.*";
 
 979 +      settings = mkOption {
 
 981 -          Attribute set whose fields overwrites fields in
 
 982 +          Settings whose options overwrite fields in
 
 983            <literal>.config/transmission-daemon/settings.json</literal>
 
 984 -          (each time the service starts). String values must be quoted, integer and
 
 985 -          boolean values must not.
 
 986 +          (each time the service starts).
 
 988            See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
 
 990 +          for documentation of settings not explicitely covered by this module.
 
 993 +        type = types.submodule {
 
 994 +          freeformType = settingsFormat.type;
 
 995 +          options.download-dir = mkOption {
 
 997 +            default = "${cfg.home}/${downloadsDir}";
 
 998 +            description = "Directory where to download torrents.";
 
1000 +          options.incomplete-dir = mkOption {
 
1001 +            type = types.path;
 
1002 +            default = "${cfg.home}/${incompleteDir}";
 
1005 +              services.transmission.home
 
1006 +              <xref linkend="opt-services.transmission.settings.incomplete-dir-enabled"/>,
 
1007 +              new torrents will download the files to this directory.
 
1008 +              When complete, the files will be moved to download-dir
 
1009 +              <xref linkend="opt-services.transmission.settings.download-dir"/>.
 
1012 +          options.incomplete-dir-enabled = mkOption {
 
1013 +            type = types.bool;
 
1017 +          options.message-level = mkOption {
 
1018 +            type = types.ints.between 0 2;
 
1020 +            description = "Set verbosity of transmission messages.";
 
1022 +          options.peer-port = mkOption {
 
1023 +            type = types.port;
 
1025 +            description = "The peer port to listen for incoming connections.";
 
1027 +          options.peer-port-random-high = mkOption {
 
1028 +            type = types.port;
 
1031 +              The maximum peer port to listen to for incoming connections
 
1032 +              when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
 
1035 +          options.peer-port-random-low = mkOption {
 
1036 +            type = types.port;
 
1039 +              The minimal peer port to listen to for incoming connections
 
1040 +              when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
 
1043 +          options.peer-port-random-on-start = mkOption {
 
1044 +            type = types.bool;
 
1046 +            description = "Randomize the peer port.";
 
1048 +          options.rpc-bind-address = mkOption {
 
1050 +            default = "127.0.0.1";
 
1051 +            example = "0.0.0.0";
 
1053 +              Where to listen for RPC connections.
 
1054 +              Use \"0.0.0.0\" to listen on all interfaces.
 
1057 +          options.rpc-port = mkOption {
 
1058 +            type = types.port;
 
1060 +            description = "The RPC port to listen to.";
 
1062 +          options.script-torrent-done-enabled = mkOption {
 
1063 +            type = types.bool;
 
1067 +              <xref linkend="opt-services.transmission.settings.script-torrent-done-filename"/>
 
1068 +              at torrent completion.
 
1071 +          options.script-torrent-done-filename = mkOption {
 
1072 +            type = types.nullOr types.path;
 
1074 +            description = "Executable to be run at torrent completion.";
 
1076 +          options.umask = mkOption {
 
1080 +              Sets transmission's file mode creation mask.
 
1081 +              See the umask(2) manpage for more information.
 
1082 +              Users who want their saved torrents to be world-writable
 
1083 +              may want to set this value to 0.
 
1084 +              Bear in mind that the json markup language only accepts numbers in base 10,
 
1085 +              so the standard umask(2) octal notation "022" is written in settings.json as 18.
 
1088 +          options.utp-enabled = mkOption {
 
1089 +            type = types.bool;
 
1092 +              Whether to enable <link xlink:href="http://en.wikipedia.org/wiki/Micro_Transport_Protocol">Micro Transport Protocol (µTP)</link>.
 
1095 +          options.watch-dir = mkOption {
 
1096 +            type = types.path;
 
1097 +            default = "${cfg.home}/${watchDir}";
 
1098 +            description = "Watch a directory for torrent files and add them to transmission.";
 
1100 +          options.watch-dir-enabled = mkOption {
 
1101 +            type = types.bool;
 
1103 +            description = ''Whether to enable the
 
1104 +              <xref linkend="opt-services.transmission.settings.watch-dir"/>.
 
1107 +          options.trash-original-torrent-files = mkOption {
 
1108 +            type = types.bool;
 
1110 +            description = ''Whether to delete torrents added from the
 
1111 +              <xref linkend="opt-services.transmission.settings.watch-dir"/>.
 
1117        downloadDirPermissions = mkOption {
 
1118 @@ -74,31 +172,22 @@ in
 
1121            The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal>
 
1122 -          on the directories <link linkend="opt-services.transmission.settings">settings.download-dir</link>
 
1123 -          and <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link>.
 
1124 +          on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>
 
1125 +          and <xref linkend="opt-services.transmission.settings.incomplete-dir"/>.
 
1126            Note that you may also want to change
 
1127 -          <link linkend="opt-services.transmission.settings">settings.umask</link>.
 
1132 -        type = types.port;
 
1134 -          TCP port number to run the RPC/web interface.
 
1136 -          If instead you want to change the peer port,
 
1137 -          use <link linkend="opt-services.transmission.settings">settings.peer-port</link>
 
1138 -          or <link linkend="opt-services.transmission.settings">settings.peer-port-random-on-start</link>.
 
1139 +          <xref linkend="opt-services.transmission.settings.umask"/>.
 
1145 -        default = homeDir;
 
1146 +        default = "/var/lib/transmission";
 
1148            The directory where Transmission will create <literal>${settingsDir}</literal>.
 
1149 -          as well as <literal>${downloadsDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.download-dir</link> is changed,
 
1150 -          and <literal>${incompleteDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link> is changed.
 
1151 +          as well as <literal>${downloadsDir}/</literal> unless
 
1152 +          <xref linkend="opt-services.transmission.settings.download-dir"/> is changed,
 
1153 +          and <literal>${incompleteDir}/</literal> unless
 
1154 +          <xref linkend="opt-services.transmission.settings.incomplete-dir"/> is changed.
 
1158 @@ -119,19 +208,22 @@ in
 
1160            Path to a JSON file to be merged with the settings.
 
1161            Useful to merge a file which is better kept out of the Nix store
 
1162 -          because it contains sensible data like <link linkend="opt-services.transmission.settings">settings.rpc-password</link>.
 
1163 +          because it contains sensible data like
 
1164 +          <xref linkend="opt-services.transmission.settings.rpc-password"/>.
 
1166          default = "/dev/null";
 
1167          example = "/var/lib/secrets/transmission/settings.json";
 
1170 -      openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
 
1171 +      openPeerPorts = mkEnableOption "opening of the peer port(s) in the firewall";
 
1173 +      openRPCPort = mkEnableOption "opening of the RPC port in the firewall";
 
1175        performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
 
1176          to open many more connections at the same time.
 
1178          Note that you may also want to increase
 
1179 -        <link linkend="opt-services.transmission.settings">settings.peer-limit-global</link>.
 
1180 +        <xref linkend="opt-services.transmission.settings.peer-limit-global"/>.
 
1181          And be aware that these settings are quite aggressive
 
1182          and might not suite your regular desktop use.
 
1183          For instance, SSH sessions may time out more easily'';
 
1184 @@ -152,40 +244,14 @@ in
 
1185        install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
 
1186        '' + optionalString cfg.settings.incomplete-dir-enabled ''
 
1187        install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
 
1188 +      '' + optionalString cfg.settings.watch-dir-enabled ''
 
1189 +      install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-dir}'
 
1193 -      { assertion = builtins.match "^/.*" cfg.home != null;
 
1194 -        message = "`services.transmission.home' must be an absolute path.";
 
1196 -      { assertion = types.path.check cfg.settings.download-dir;
 
1197 -        message = "`services.transmission.settings.download-dir' must be an absolute path.";
 
1199 -      { assertion = types.path.check cfg.settings.incomplete-dir;
 
1200 -        message = "`services.transmission.settings.incomplete-dir' must be an absolute path.";
 
1202 -      { assertion = types.path.check cfg.settings.watch-dir;
 
1203 -        message = "`services.transmission.settings.watch-dir' must be an absolute path.";
 
1205 -      { assertion = cfg.settings.script-torrent-done-filename == "" || types.path.check cfg.settings.script-torrent-done-filename;
 
1206 -        message = "`services.transmission.settings.script-torrent-done-filename' must be an absolute path.";
 
1208 -      { assertion = types.port.check cfg.settings.rpc-port;
 
1209 -        message = "${toString cfg.settings.rpc-port} is not a valid port number for `services.transmission.settings.rpc-port`.";
 
1211 -      # In case both port and settings.rpc-port are explicitely defined: they must be the same.
 
1212 -      { assertion = !options.services.transmission.port.isDefined || cfg.port == cfg.settings.rpc-port;
 
1213 -        message = "`services.transmission.port' is not equal to `services.transmission.settings.rpc-port'";
 
1217 -    services.transmission.settings =
 
1218 -      optionalAttrs options.services.transmission.port.isDefined { rpc-port = cfg.port; };
 
1220      systemd.services.transmission = {
 
1221        description = "Transmission BitTorrent Service";
 
1222 -      after = [ "network.target" ] ++ optional apparmor "apparmor.service";
 
1223 -      requires = optional apparmor "apparmor.service";
 
1224 +      after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service";
 
1225 +      requires = optional apparmor.enable "apparmor.service";
 
1226        wantedBy = [ "multi-user.target" ];
 
1227        environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
 
1229 @@ -226,11 +292,9 @@ in
 
1230              cfg.settings.download-dir
 
1232            optional cfg.settings.incomplete-dir-enabled
 
1233 -            cfg.settings.incomplete-dir
 
1235 -          optional cfg.settings.watch-dir-enabled
 
1236 -            cfg.settings.watch-dir
 
1238 +            cfg.settings.incomplete-dir ++
 
1239 +          optional (cfg.settings.watch-dir-enabled && cfg.settings.trash-original-torrent-files)
 
1240 +            cfg.settings.watch-dir;
 
1241          BindReadOnlyPaths = [
 
1242            # No confinement done of /nix/store here like in systemd-confinement.nix,
 
1243            # an AppArmor profile is provided to get a confinement based upon paths and rights.
 
1244 @@ -239,8 +303,10 @@ in
 
1247            optional (cfg.settings.script-torrent-done-enabled &&
 
1248 -                    cfg.settings.script-torrent-done-filename != "")
 
1249 -            cfg.settings.script-torrent-done-filename;
 
1250 +                    cfg.settings.script-torrent-done-filename != null)
 
1251 +            cfg.settings.script-torrent-done-filename ++
 
1252 +          optional (cfg.settings.watch-dir-enabled && !cfg.settings.trash-original-torrent-files)
 
1253 +            cfg.settings.watch-dir;
 
1254          # The following options are only for optimizing:
 
1255          # systemd-analyze security transmission
 
1256          AmbientCapabilities = "";
 
1257 @@ -307,25 +373,28 @@ in
 
1261 -    networking.firewall = mkIf cfg.openFirewall (
 
1262 -      if cfg.settings.peer-port-random-on-start
 
1264 -        { allowedTCPPortRanges =
 
1265 -            [ { from = cfg.settings.peer-port-random-low;
 
1266 -                to   = cfg.settings.peer-port-random-high;
 
1269 -          allowedUDPPortRanges =
 
1270 -            [ { from = cfg.settings.peer-port-random-low;
 
1271 -                to   = cfg.settings.peer-port-random-high;
 
1276 -        { allowedTCPPorts = [ cfg.settings.peer-port ];
 
1277 -          allowedUDPPorts = [ cfg.settings.peer-port ];
 
1280 +    networking.firewall = mkMerge [
 
1281 +      (mkIf cfg.openPeerPorts (
 
1282 +        if cfg.settings.peer-port-random-on-start
 
1284 +          { allowedTCPPortRanges =
 
1285 +              [ { from = cfg.settings.peer-port-random-low;
 
1286 +                  to   = cfg.settings.peer-port-random-high;
 
1289 +            allowedUDPPortRanges =
 
1290 +              [ { from = cfg.settings.peer-port-random-low;
 
1291 +                  to   = cfg.settings.peer-port-random-high;
 
1296 +          { allowedTCPPorts = [ cfg.settings.peer-port ];
 
1297 +            allowedUDPPorts = [ cfg.settings.peer-port ];
 
1300 +      (mkIf cfg.openRPCPort { allowedTCPPorts = [ cfg.settings.rpc-port ]; })
 
1303      boot.kernel.sysctl = mkMerge [
 
1304        # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
 
1305 @@ -340,113 +409,57 @@ in
 
1306          # Increase the number of available source (local) TCP and UDP ports to 49151.
 
1307          # Usual default is 32768 60999, ie. 28231 ports.
 
1308          # Find out your current usage with: ss -s
 
1309 -        "net.ipv4.ip_local_port_range" = "16384 65535";
 
1310 +        "net.ipv4.ip_local_port_range" = mkDefault "16384 65535";
 
1311          # Timeout faster generic TCP states.
 
1312          # Usual default is 600.
 
1313          # Find out your current usage with: watch -n 1 netstat -nptuo
 
1314 -        "net.netfilter.nf_conntrack_generic_timeout" = 60;
 
1315 +        "net.netfilter.nf_conntrack_generic_timeout" = mkDefault 60;
 
1316          # Timeout faster established but inactive connections.
 
1317          # Usual default is 432000.
 
1318 -        "net.netfilter.nf_conntrack_tcp_timeout_established" = 600;
 
1319 +        "net.netfilter.nf_conntrack_tcp_timeout_established" = mkDefault 600;
 
1320          # Clear immediately TCP states after timeout.
 
1321          # Usual default is 120.
 
1322 -        "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = 1;
 
1323 +        "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = mkDefault 1;
 
1324          # Increase the number of trackable connections.
 
1325          # Usual default is 262144.
 
1326          # Find out your current usage with: conntrack -C
 
1327 -        "net.netfilter.nf_conntrack_max" = 1048576;
 
1328 +        "net.netfilter.nf_conntrack_max" = mkDefault 1048576;
 
1332 -    security.apparmor.profiles = mkIf apparmor [
 
1333 -      (pkgs.writeText "apparmor-transmission-daemon" ''
 
1334 -        include <tunables/global>
 
1335 +    security.apparmor.policies."bin.transmission-daemon".profile = ''
 
1336 +      include "${pkgs.transmission.apparmor}/bin.transmission-daemon"
 
1338 +    security.apparmor.includes."local/bin.transmission-daemon" = ''
 
1339 +      r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
 
1341 -        ${pkgs.transmission}/bin/transmission-daemon {
 
1342 -          include <abstractions/base>
 
1343 -          include <abstractions/nameservice>
 
1344 +      owner rw ${cfg.home}/${settingsDir}/**,
 
1345 +      rw ${cfg.settings.download-dir}/**,
 
1346 +      ${optionalString cfg.settings.incomplete-dir-enabled ''
 
1347 +        rw ${cfg.settings.incomplete-dir}/**,
 
1349 +      ${optionalString cfg.settings.watch-dir-enabled ''
 
1350 +        r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
 
1353 +        rw ${cfg.settings.download-dir}/**,
 
1354 +        ${optionalString cfg.settings.incomplete-dir-enabled ''
 
1355 +          rw ${cfg.settings.incomplete-dir}/**,
 
1357 +        ${optionalString cfg.settings.watch-dir-enabled ''
 
1358 +          r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
 
1362 -          # NOTE: https://github.com/NixOS/nixpkgs/pull/93457
 
1363 -          # will remove the need for these by fixing <abstractions/base>
 
1364 -          r ${etc."hosts".source},
 
1365 -          r /etc/ld-nix.so.preload,
 
1366 -          ${lib.optionalString (builtins.hasAttr "ld-nix.so.preload" etc) ''
 
1367 -            r ${etc."ld-nix.so.preload".source},
 
1368 -            ${concatMapStrings (p: optionalString (p != "") ("mr ${p},\n"))
 
1369 -              (splitString "\n" config.environment.etc."ld-nix.so.preload".text)}
 
1371 -          r ${etc."ssl/certs/ca-certificates.crt".source},
 
1372 -          r ${pkgs.tzdata}/share/zoneinfo/**,
 
1373 -          r ${pkgs.stdenv.cc.libc}/share/i18n/**,
 
1374 -          r ${pkgs.stdenv.cc.libc}/share/locale/**,
 
1376 -          mr ${getLib pkgs.stdenv.cc.cc}/lib/*.so*,
 
1377 -          mr ${getLib pkgs.stdenv.cc.libc}/lib/*.so*,
 
1378 -          mr ${getLib pkgs.attr}/lib/libattr*.so*,
 
1379 -          mr ${getLib pkgs.c-ares}/lib/libcares*.so*,
 
1380 -          mr ${getLib pkgs.curl}/lib/libcurl*.so*,
 
1381 -          mr ${getLib pkgs.keyutils}/lib/libkeyutils*.so*,
 
1382 -          mr ${getLib pkgs.libcap}/lib/libcap*.so*,
 
1383 -          mr ${getLib pkgs.libevent}/lib/libevent*.so*,
 
1384 -          mr ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so*,
 
1385 -          mr ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so*,
 
1386 -          mr ${getLib pkgs.libkrb5}/lib/lib*.so*,
 
1387 -          mr ${getLib pkgs.libssh2}/lib/libssh2*.so*,
 
1388 -          mr ${getLib pkgs.lz4}/lib/liblz4*.so*,
 
1389 -          mr ${getLib pkgs.nghttp2}/lib/libnghttp2*.so*,
 
1390 -          mr ${getLib pkgs.openssl}/lib/libcrypto*.so*,
 
1391 -          mr ${getLib pkgs.openssl}/lib/libssl*.so*,
 
1392 -          mr ${getLib pkgs.systemd}/lib/libsystemd*.so*,
 
1393 -          mr ${getLib pkgs.util-linuxMinimal.out}/lib/libblkid.so*,
 
1394 -          mr ${getLib pkgs.util-linuxMinimal.out}/lib/libmount.so*,
 
1395 -          mr ${getLib pkgs.util-linuxMinimal.out}/lib/libuuid.so*,
 
1396 -          mr ${getLib pkgs.xz}/lib/liblzma*.so*,
 
1397 -          mr ${getLib pkgs.zlib}/lib/libz*.so*,
 
1399 -          r @{PROC}/sys/kernel/random/uuid,
 
1400 -          r @{PROC}/sys/vm/overcommit_memory,
 
1401 -          # @{pid} is not a kernel variable yet but a regexp
 
1402 -          #r @{PROC}/@{pid}/environ,
 
1403 -          r @{PROC}/@{pid}/mounts,
 
1404 -          rwk /tmp/tr_session_id_*,
 
1405 -          r /run/systemd/resolve/stub-resolv.conf,
 
1407 -          r ${pkgs.openssl.out}/etc/**,
 
1408 -          r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
 
1409 -          r ${pkgs.transmission}/share/transmission/**,
 
1411 -          owner rw ${cfg.home}/${settingsDir}/**,
 
1412 -          rw ${cfg.settings.download-dir}/**,
 
1413 -          ${optionalString cfg.settings.incomplete-dir-enabled ''
 
1414 -            rw ${cfg.settings.incomplete-dir}/**,
 
1416 -          ${optionalString cfg.settings.watch-dir-enabled ''
 
1417 -            rw ${cfg.settings.watch-dir}/**,
 
1420 -            rw ${cfg.settings.download-dir}/**,
 
1421 -            ${optionalString cfg.settings.incomplete-dir-enabled ''
 
1422 -              rw ${cfg.settings.incomplete-dir}/**,
 
1424 -            ${optionalString cfg.settings.watch-dir-enabled ''
 
1425 -              rw ${cfg.settings.watch-dir}/**,
 
1429 -          ${optionalString (cfg.settings.script-torrent-done-enabled &&
 
1430 -                            cfg.settings.script-torrent-done-filename != "") ''
 
1431 -            # Stack transmission_directories profile on top of
 
1432 -            # any existing profile for script-torrent-done-filename
 
1433 -            # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
 
1434 -            # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
 
1435 -            px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
 
1438 -          # FIXME: enable customizing using https://github.com/NixOS/nixpkgs/pull/93457
 
1439 -          # include <local/transmission-daemon>
 
1443 +      ${optionalString (cfg.settings.script-torrent-done-enabled &&
 
1444 +                        cfg.settings.script-torrent-done-filename != null) ''
 
1445 +        # Stack transmission_directories profile on top of
 
1446 +        # any existing profile for script-torrent-done-filename
 
1447 +        # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
 
1448 +        # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
 
1449 +        px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
 
1454    meta.maintainers = with lib.maintainers; [ julm ];
 
1455 diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
 
1456 index f730ec82bdf..3bfb59a8848 100644
 
1457 --- a/nixos/modules/tasks/network-interfaces.nix
 
1458 +++ b/nixos/modules/tasks/network-interfaces.nix
 
1459 @@ -1111,6 +1111,21 @@ in
 
1461        ping.source = "${pkgs.iputils.out}/bin/ping";
 
1463 +    security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter ''
 
1464 +      /run/wrappers/bin/ping {
 
1465 +        include <abstractions/base>
 
1466 +        include <nixos/security.wrappers>
 
1467 +        rpx /run/wrappers/wrappers.*/ping,
 
1469 +      /run/wrappers/wrappers.*/ping {
 
1470 +        include <abstractions/base>
 
1471 +        include <nixos/security.wrappers>
 
1472 +        r /run/wrappers/wrappers.*/ping.real,
 
1473 +        mrpx ${config.security.wrappers.ping.source},
 
1474 +        capability net_raw,
 
1475 +        capability setpcap,
 
1479      # Set the host and domain names in the activation script.  Don't
 
1480      # clear it if it's not configured in the NixOS configuration,
 
1481 diff --git a/nixos/modules/virtualisation/lxc.nix b/nixos/modules/virtualisation/lxc.nix
 
1482 index f484d5ee59a..0f8b22a45df 100644
 
1483 --- a/nixos/modules/virtualisation/lxc.nix
 
1484 +++ b/nixos/modules/virtualisation/lxc.nix
 
1485 @@ -74,9 +74,13 @@ in
 
1486      systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
 
1488      security.apparmor.packages = [ pkgs.lxc ];
 
1489 -    security.apparmor.profiles = [
 
1490 -      "${pkgs.lxc}/etc/apparmor.d/lxc-containers"
 
1491 -      "${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start"
 
1493 +    security.apparmor.policies = {
 
1494 +      "bin.lxc-start".profile = ''
 
1495 +        include ${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start
 
1497 +      "lxc-containers".profile = ''
 
1498 +        include ${pkgs.lxc}/etc/apparmor.d/lxc-containers
 
1503 diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
 
1504 index 96e8d68ae50..6b6f4b6e652 100644
 
1505 --- a/nixos/modules/virtualisation/lxd.nix
 
1506 +++ b/nixos/modules/virtualisation/lxd.nix
 
1507 @@ -97,11 +97,17 @@ in {
 
1508      # does a bunch of unrelated things.
 
1509      systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
 
1511 -    security.apparmor.packages = [ cfg.lxcPackage ];
 
1512 -    security.apparmor.profiles = [
 
1513 -      "${cfg.lxcPackage}/etc/apparmor.d/lxc-containers"
 
1514 -      "${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start"
 
1516 +    security.apparmor = {
 
1517 +      packages = [ cfg.lxcPackage ];
 
1519 +        "bin.lxc-start".profile = ''
 
1520 +          include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
 
1522 +        "lxc-containers".profile = ''
 
1523 +          include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
 
1528      # TODO: remove once LXD gets proper support for cgroupsv2
 
1529      # (currently most of the e.g. CPU accounting stuff doesn't work)
 
1530 diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
 
1531 index fb45ec1a310..957d052ace1 100644
 
1532 --- a/nixos/tests/all-tests.nix
 
1533 +++ b/nixos/tests/all-tests.nix
 
1534 @@ -25,6 +25,7 @@ in
 
1535    acme = handleTest ./acme.nix {};
 
1536    agda = handleTest ./agda.nix {};
 
1537    ammonite = handleTest ./ammonite.nix {};
 
1538 +  apparmor = handleTest ./apparmor.nix {};
 
1539    atd = handleTest ./atd.nix {};
 
1540    avahi = handleTest ./avahi.nix {};
 
1541    avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
 
1542 diff --git a/nixos/tests/apparmor.nix b/nixos/tests/apparmor.nix
 
1543 new file mode 100644
 
1544 index 00000000000..c6daa8e67de
 
1546 +++ b/nixos/tests/apparmor.nix
 
1548 +import ./make-test-python.nix ({ pkgs, ... } : {
 
1549 +  name = "apparmor";
 
1550 +  meta = with pkgs.lib.maintainers; {
 
1551 +    maintainers = [ julm ];
 
1555 +    { lib, pkgs, config, ... }:
 
1558 +      security.apparmor.enable = mkDefault true;
 
1563 +      machine.wait_for_unit("multi-user.target")
 
1565 +      with subtest("AppArmor profiles are loaded"):
 
1566 +          machine.succeed("systemctl status apparmor.service")
 
1568 +      # AppArmor securityfs
 
1569 +      with subtest("AppArmor securityfs is mounted"):
 
1570 +          machine.succeed("mountpoint -q /sys/kernel/security")
 
1571 +          machine.succeed("cat /sys/kernel/security/apparmor/profiles")
 
1573 +      # Test apparmorRulesFromClosure by:
 
1574 +      # 1. Prepending a string of the relevant packages' name and version on each line.
 
1575 +      # 2. Sorting according to those strings.
 
1576 +      # 3. Removing those prepended strings.
 
1577 +      # 4. Using `diff` against the expected output.
 
1578 +      with subtest("apparmorRulesFromClosure"):
 
1580 +              "${pkgs.diffutils}/bin/diff ${pkgs.writeText "expected.rules" ''
 
1581 +                  mr ${pkgs.bash}/lib/**.so*,
 
1583 +                  r ${pkgs.bash}/etc/**,
 
1584 +                  r ${pkgs.bash}/lib/**,
 
1585 +                  r ${pkgs.bash}/share/**,
 
1586 +                  x ${pkgs.bash}/foo/**,
 
1587 +                  mr ${pkgs.glibc}/lib/**.so*,
 
1589 +                  r ${pkgs.glibc}/etc/**,
 
1590 +                  r ${pkgs.glibc}/lib/**,
 
1591 +                  r ${pkgs.glibc}/share/**,
 
1592 +                  x ${pkgs.glibc}/foo/**,
 
1593 +                  mr ${pkgs.libcap}/lib/**.so*,
 
1595 +                  r ${pkgs.libcap}/etc/**,
 
1596 +                  r ${pkgs.libcap}/lib/**,
 
1597 +                  r ${pkgs.libcap}/share/**,
 
1598 +                  x ${pkgs.libcap}/foo/**,
 
1599 +                  mr ${pkgs.libcap.lib}/lib/**.so*,
 
1600 +                  r ${pkgs.libcap.lib},
 
1601 +                  r ${pkgs.libcap.lib}/etc/**,
 
1602 +                  r ${pkgs.libcap.lib}/lib/**,
 
1603 +                  r ${pkgs.libcap.lib}/share/**,
 
1604 +                  x ${pkgs.libcap.lib}/foo/**,
 
1605 +                  mr ${pkgs.libidn2.out}/lib/**.so*,
 
1606 +                  r ${pkgs.libidn2.out},
 
1607 +                  r ${pkgs.libidn2.out}/etc/**,
 
1608 +                  r ${pkgs.libidn2.out}/lib/**,
 
1609 +                  r ${pkgs.libidn2.out}/share/**,
 
1610 +                  x ${pkgs.libidn2.out}/foo/**,
 
1611 +                  mr ${pkgs.libunistring}/lib/**.so*,
 
1612 +                  r ${pkgs.libunistring},
 
1613 +                  r ${pkgs.libunistring}/etc/**,
 
1614 +                  r ${pkgs.libunistring}/lib/**,
 
1615 +                  r ${pkgs.libunistring}/share/**,
 
1616 +                  x ${pkgs.libunistring}/foo/**,
 
1617 +              ''} ${pkgs.runCommand "actual.rules" { preferLocalBuild = true; } ''
 
1618 +                  ${pkgs.gnused}/bin/sed -e 's:^[^ ]* ${builtins.storeDir}/[^,/-]*-\([^/,]*\):\1 \0:' ${
 
1619 +                      pkgs.apparmorRulesFromClosure {
 
1621 +                        additionalRules = ["x $path/foo/**"];
 
1624 +                  ${pkgs.coreutils}/bin/sort -n -k1 |
 
1625 +                  ${pkgs.gnused}/bin/sed -e 's:^[^ ]* ::' >$out
 
1630 diff --git a/pkgs/applications/networking/p2p/transmission/default.nix b/pkgs/applications/networking/p2p/transmission/default.nix
 
1631 index 7e8b6b671cd..b2519eb2fa0 100644
 
1632 --- a/pkgs/applications/networking/p2p/transmission/default.nix
 
1633 +++ b/pkgs/applications/networking/p2p/transmission/default.nix
 
1635  , enableSystemd ? stdenv.isLinux
 
1636  , enableDaemon ? true
 
1638 +, apparmorRulesFromClosure
 
1642 @@ -37,6 +38,8 @@ in stdenv.mkDerivation {
 
1643      fetchSubmodules = true;
 
1646 +  outputs = [ "out" "apparmor" ];
 
1650        mkFlag = opt: if opt then "ON" else "OFF";
 
1651 @@ -72,6 +75,30 @@ in stdenv.mkDerivation {
 
1653    NIX_LDFLAGS = lib.optionalString stdenv.isDarwin "-framework CoreFoundation";
 
1656 +    install -D -m 644 /dev/stdin $apparmor/bin.transmission-daemon <<EOF
 
1657 +    include <tunables/global>
 
1658 +    $out/bin/transmission-daemon {
 
1659 +      include <abstractions/base>
 
1660 +      include <abstractions/nameservice>
 
1661 +      include <abstractions/ssl_certs>
 
1662 +      include "${apparmorRulesFromClosure { name = "transmission-daemon"; } ([
 
1663 +        curl libevent openssl pcre zlib
 
1664 +      ] ++ lib.optionals enableSystemd [ systemd ]
 
1665 +        ++ lib.optionals stdenv.isLinux [ inotify-tools ]
 
1667 +      r @{PROC}/sys/kernel/random/uuid,
 
1668 +      r @{PROC}/sys/vm/overcommit_memory,
 
1669 +      r @{PROC}/@{pid}/environ,
 
1670 +      r @{PROC}/@{pid}/mounts,
 
1671 +      rwk /tmp/tr_session_id_*,
 
1672 +      r /run/systemd/resolve/stub-resolv.conf,
 
1674 +      include <local/bin.transmission-daemon>
 
1680      description = "A fast, easy and free BitTorrent client";
 
1681      longDescription = ''
 
1682 diff --git a/pkgs/os-specific/linux/apparmor/default.nix b/pkgs/os-specific/linux/apparmor/default.nix
 
1683 index 935b5e65b1f..a07cd5070d2 100644
 
1684 --- a/pkgs/os-specific/linux/apparmor/default.nix
 
1685 +++ b/pkgs/os-specific/linux/apparmor/default.nix
 
1700 -  apparmor-series = "2.13";
 
1701 -  apparmor-patchver = "6";
 
1702 -  apparmor-version = apparmor-series + "." + apparmor-patchver;
 
1703 +  apparmor-version = "3.0.1";
 
1705    apparmor-meta = component: with lib; {
 
1706      homepage = "https://apparmor.net/";
 
1707      description = "A mandatory access control system - ${component}";
 
1708      license = licenses.gpl2;
 
1709 -    maintainers = with maintainers; [ phreedom thoughtpolice joachifm ];
 
1710 +    maintainers = with maintainers; [ joachifm julm phreedom thoughtpolice ];
 
1711      platforms = platforms.linux;
 
1714    apparmor-sources = fetchurl {
 
1715 -    url = "https://launchpad.net/apparmor/${apparmor-series}/${apparmor-version}/+download/apparmor-${apparmor-version}.tar.gz";
 
1716 -    sha256 = "13xshy7905d9q9n8d8i0jmdi9m36wr525g4wlsp8k21n7yvvh9j4";
 
1717 +    url = "https://launchpad.net/apparmor/${lib.versions.majorMinor apparmor-version}/${apparmor-version}/+download/apparmor-${apparmor-version}.tar.gz";
 
1718 +    sha256 = "096zbg3v7b51x7f1ly61mzd3iy9alad6sd4lam98j2d6v5ragbcg";
 
1721 +  aa-teardown = writeShellScript "aa-teardown" ''
 
1722 +    PATH="${lib.makeBinPath [coreutils gnused gnugrep]}:$PATH"
 
1723 +    . ${apparmor-parser}/lib/apparmor/rc.apparmor.functions
 
1728      chmod a+x ./common/list_capabilities.sh ./common/list_af_names.sh
 
1729      patchShebangs ./common/list_capabilities.sh ./common/list_af_names.sh
 
1730 @@ -45,12 +56,6 @@ let
 
1731        name = "0003-Added-missing-typedef-definitions-on-parser.patch";
 
1732        sha256 = "0yyaqz8jlmn1bm37arggprqz0njb4lhjni2d9c8qfqj0kll0bam0";
 
1735 -      url = "https://git.alpinelinux.org/aports/plain/testing/apparmor/0007-Do-not-build-install-vim-file-with-utils-package.patch?id=74b8427cc21f04e32030d047ae92caa618105b53";
 
1736 -      name = "0007-Do-not-build-install-vim-file-with-utils-package.patch";
 
1737 -      sha256 = "1m4dx901biqgnr4w4wz8a2z9r9dxyw7wv6m6mqglqwf2lxinqmp4";
 
1739 -    # (alpine patches {1,4,5,6,8} are needed for apparmor 2.11, but not 2.12)
 
1742    # Set to `true` after the next FIXME gets fixed or this gets some
 
1743 @@ -121,7 +126,11 @@ let
 
1747 -    prePatch = prePatchCommon + ''
 
1748 +    prePatch = prePatchCommon +
 
1749 +      # Do not build vim file
 
1750 +      lib.optionalString stdenv.hostPlatform.isMusl ''
 
1751 +        sed -i ./utils/Makefile -e "/\<vim\>/d"
 
1753        substituteInPlace ./utils/apparmor/easyprof.py --replace "/sbin/apparmor_parser" "${apparmor-parser}/bin/apparmor_parser"
 
1754        substituteInPlace ./utils/apparmor/aa.py --replace "/sbin/apparmor_parser" "${apparmor-parser}/bin/apparmor_parser"
 
1755        substituteInPlace ./utils/logprof.conf --replace "/sbin/apparmor_parser" "${apparmor-parser}/bin/apparmor_parser"
 
1756 @@ -132,7 +141,8 @@ let
 
1757      installFlags = [ "DESTDIR=$(out)" "BINDIR=$(out)/bin" "VIM_INSTALL_PATH=$(out)/share" "PYPREFIX=" ];
 
1760 -      for prog in aa-audit aa-autodep aa-cleanprof aa-complain aa-disable aa-enforce aa-genprof aa-logprof aa-mergeprof aa-status aa-unconfined ; do
 
1761 +      sed -i $out/bin/aa-unconfined -e "/my_env\['PATH'\]/d"
 
1762 +      for prog in aa-audit aa-autodep aa-cleanprof aa-complain aa-disable aa-enforce aa-genprof aa-logprof aa-mergeprof aa-unconfined ; do
 
1763          wrapProgram $out/bin/$prog --prefix PYTHONPATH : "$out/lib/${python.libPrefix}/site-packages:$PYTHONPATH"
 
1766 @@ -140,6 +150,13 @@ let
 
1767        # aa-notify checks its name and does not work named ".aa-notify-wrapped"
 
1768        mv $out/bin/aa-notify $out/bin/aa-notify-wrapped
 
1769        makeWrapper ${perl}/bin/perl $out/bin/aa-notify --set PERL5LIB ${libapparmor}/${perl.libPrefix} --add-flags $out/bin/aa-notify-wrapped
 
1771 +      substituteInPlace $out/bin/aa-remove-unknown \
 
1772 +       --replace "/lib/apparmor/rc.apparmor.functions" "${apparmor-parser}/lib/apparmor/rc.apparmor.functions"
 
1773 +      wrapProgram $out/bin/aa-remove-unknown \
 
1774 +       --prefix PATH : ${lib.makeBinPath [gawk]}
 
1776 +      ln -s ${aa-teardown} $out/bin/aa-teardown
 
1780 @@ -167,7 +184,7 @@ let
 
1781      prePatch = prePatchCommon;
 
1782      postPatch = "cd ./binutils";
 
1783      makeFlags = [ "LANGS=" "USE_SYSTEM=1" ];
 
1784 -    installFlags = [ "DESTDIR=$(out)" "BINDIR=$(out)/bin" ];
 
1785 +    installFlags = [ "DESTDIR=$(out)" "BINDIR=$(out)/bin" "SBINDIR=$(out)/bin" ];
 
1789 @@ -188,6 +205,9 @@ let
 
1790        substituteInPlace ./parser/Makefile --replace "/usr/include/linux/capability.h" "${linuxHeaders}/include/linux/capability.h"
 
1791        ## techdoc.pdf still doesn't build ...
 
1792        substituteInPlace ./parser/Makefile --replace "manpages htmlmanpages pdf" "manpages htmlmanpages"
 
1793 +      substituteInPlace parser/rc.apparmor.functions \
 
1794 +       --replace "/sbin/apparmor_parser" "$out/bin/apparmor_parser"
 
1795 +      sed -i parser/rc.apparmor.functions -e '2i . ${./fix-rc.apparmor.functions.sh}'
 
1798      postPatch = "cd ./parser";
 
1799 @@ -249,8 +269,35 @@ let
 
1800      meta = apparmor-meta "kernel patches";
 
1803 +  # Generate generic AppArmor rules in a file,
 
1804 +  # from the closure of given rootPaths.
 
1805 +  # To be included in an AppArmor profile like so:
 
1806 +  # include "$(apparmorRulesFromClosure {} [pkgs.hello]}"
 
1807 +  apparmorRulesFromClosure =
 
1808 +    { # The store path of the derivation is given in $path
 
1809 +      additionalRules ? []
 
1810 +      # TODO: factorize here some other common paths
 
1811 +      # that may emerge from use cases.
 
1815 +        "r $path/share/**"
 
1816 +        # Note that not all libraries are prefixed with "lib",
 
1817 +        # eg. glibc-2.30/lib/ld-2.30.so
 
1818 +        "mr $path/lib/**.so*"
 
1819 +        # eg. glibc-2.30/lib/gconv/gconv-modules
 
1823 +    }: rootPaths: runCommand
 
1824 +      ( "apparmor-closure-rules"
 
1825 +      + lib.optionalString (name != "") "-${name}" ) {} ''
 
1827 +    while read -r path
 
1828 +    do printf >>$out "%s,\n" ${lib.concatMapStringsSep " " (x: "\"${x}\"") (baseRules ++ additionalRules)}
 
1829 +    done <${closureInfo {inherit rootPaths;}}/store-paths
 
1836 @@ -259,5 +306,6 @@ in
 
1840 -    apparmor-kernel-patches;
 
1841 +    apparmor-kernel-patches
 
1842 +    apparmorRulesFromClosure;
 
1844 diff --git a/pkgs/os-specific/linux/apparmor/fix-rc.apparmor.functions.sh b/pkgs/os-specific/linux/apparmor/fix-rc.apparmor.functions.sh
 
1845 new file mode 100644
 
1846 index 00000000000..ebc1baaa92d
 
1848 +++ b/pkgs/os-specific/linux/apparmor/fix-rc.apparmor.functions.sh
 
1855 +  if [ $rc -eq 0 ] ; then
 
1856 +    aa_log_success_msg $"$STRING "
 
1858 +    aa_log_failure_msg $"$STRING "
 
1863 +aa_log_success_msg() {
 
1864 +   [ -n "$1" ] && echo -n $1
 
1868 +aa_log_warning_msg() {
 
1869 +   [ -n "$1" ] && echo -n $1
 
1873 +aa_log_failure_msg() {
 
1874 +   [ -n "$1" ] && echo -n $1
 
1878 +aa_log_skipped_msg() {
 
1879 +   [ -n "$1" ] && echo -n $1
 
1882 diff --git a/pkgs/os-specific/linux/iputils/default.nix b/pkgs/os-specific/linux/iputils/default.nix
 
1883 index 56942d6d420..122a9ca1b7b 100644
 
1884 --- a/pkgs/os-specific/linux/iputils/default.nix
 
1885 +++ b/pkgs/os-specific/linux/iputils/default.nix
 
1887  { lib, stdenv, fetchFromGitHub
 
1888  , meson, ninja, pkg-config, gettext, libxslt, docbook_xsl_ns
 
1890 +, apparmorRulesFromClosure
 
1894 @@ -20,6 +21,8 @@ in stdenv.mkDerivation rec {
 
1895      sha256 = "08j2hfgnfh31vv9rn1ml7090j2lsvm9wdpdz13rz60rmyzrx9dq3";
 
1898 +  outputs = ["out" "apparmor"];
 
1901      "-DBUILD_RARPD=true"
 
1902      "-DBUILD_TRACEROUTE6=true"
 
1903 @@ -34,6 +37,25 @@ in stdenv.mkDerivation rec {
 
1904    nativeBuildInputs = [ meson ninja pkg-config gettext libxslt.bin docbook_xsl_ns ];
 
1905    buildInputs = [ libcap ]
 
1906      ++ lib.optional (!stdenv.hostPlatform.isMusl) libidn2;
 
1908 +    install -D -m 644 /dev/stdin $apparmor/bin.ping <<EOF
 
1909 +    include <tunables/global>
 
1911 +      include <abstractions/base>
 
1912 +      include <abstractions/consoles>
 
1913 +      include <abstractions/nameservice>
 
1914 +      include "${apparmorRulesFromClosure { name = "ping"; }
 
1915 +       ([libcap] ++ lib.optional (!stdenv.hostPlatform.isMusl) libidn2)}"
 
1916 +      include <local/bin.ping>
 
1917 +      capability net_raw,
 
1919 +      network inet6 raw,
 
1921 +      r $out/share/locale/**,
 
1922 +      r @{PROC}/@{pid}/environ,
 
1928      description = "A set of small useful utilities for Linux networking";
 
1929 diff --git a/pkgs/tools/networking/inetutils/default.nix b/pkgs/tools/networking/inetutils/default.nix
 
1930 index 1290ec2bdb1..fe5a0d91585 100644
 
1931 --- a/pkgs/tools/networking/inetutils/default.nix
 
1932 +++ b/pkgs/tools/networking/inetutils/default.nix
 
1934 -{ stdenv, lib, fetchurl, ncurses, perl, help2man }:
 
1935 +{ stdenv, lib, fetchurl, ncurses, perl, help2man
 
1936 +, apparmorRulesFromClosure
 
1939  stdenv.mkDerivation rec {
 
1940    name = "inetutils-1.9.4";
 
1941 @@ -8,6 +10,8 @@ stdenv.mkDerivation rec {
 
1942      sha256 = "05n65k4ixl85dc6rxc51b1b732gnmm8xnqi424dy9f1nz7ppb3xy";
 
1945 +  outputs = ["out" "apparmor"];
 
1948      ./whois-Update-Canadian-TLD-server.patch
 
1949      ./service-name.patch
 
1950 @@ -41,6 +45,22 @@ stdenv.mkDerivation rec {
 
1952    installFlags = [ "SUIDMODE=" ];
 
1955 +    install -D -m 644 /dev/stdin $apparmor/bin.ping <<EOF
 
1957 +      include <abstractions/base>
 
1958 +      include <abstractions/consoles>
 
1959 +      include <abstractions/nameservice>
 
1960 +      include "${apparmorRulesFromClosure { name = "ping"; } [stdenv.cc.libc]}"
 
1961 +      include <local/bin.ping>
 
1962 +      capability net_raw,
 
1964 +      network inet6 raw,
 
1971      description = "Collection of common network programs";
 
1973 diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
 
1974 index 8dfaf25fc04..3c055686e2e 100644
 
1975 --- a/pkgs/top-level/all-packages.nix
 
1976 +++ b/pkgs/top-level/all-packages.nix
 
1977 @@ -19105,7 +19105,7 @@ in
 
1979    inherit (callPackages ../os-specific/linux/apparmor { python = python3; })
 
1980      libapparmor apparmor-utils apparmor-bin-utils apparmor-parser apparmor-pam
 
1981 -    apparmor-profiles apparmor-kernel-patches;
 
1982 +    apparmor-profiles apparmor-kernel-patches apparmorRulesFromClosure;
 
1984    aseq2json = callPackage ../os-specific/linux/aseq2json {};