diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml
index 97b94c5756a..932e82ea8fe 100644
--- a/nixos/doc/manual/release-notes/rl-2009.xml
+++ b/nixos/doc/manual/release-notes/rl-2009.xml
@@ -623,6 +623,37 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
was removed, as udev gained native support to handle FIDO security tokens.
+
+
+ The services.transmission module
+ was enhanced with the new options:
+ ,
+ ,
+ and .
+
+
+ transmission-daemon is now started with additional systemd sandbox/hardening options for better security.
+ Please report
+ any use case where this is not working well.
+ In particular, the RootDirectory option newly set
+ forbids uploading or downloading a torrent outside of the default directory
+ configured at settings.download-dir.
+ If you really need Transmission to access other directories,
+ you must include those directories into the BindPaths of the service:
+
+systemd.services.transmission.serviceConfig.BindPaths = [ "/path/to/alternative/download-dir" ];
+
+
+
+ Also, connection to the RPC (Remote Procedure Call) of transmission-daemon
+ is now only available on the local network interface by default.
+ Use:
+
+services.transmission.settings.rpc-bind-address = "0.0.0.0";
+
+ to get the previous behavior of listening on all network interfaces.
+
+
With this release systemd-networkd (when enabled through )
diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
index ac2a024eaa8..0519812df88 100644
--- a/nixos/modules/config/fonts/fontconfig.nix
+++ b/nixos/modules/config/fonts/fontconfig.nix
@@ -489,6 +489,38 @@ in
(mkIf cfg.enable {
environment.systemPackages = [ pkgs.fontconfig ];
environment.etc.fonts.source = "${fontconfigEtc}/etc/fonts/";
+ security.apparmor.includes."abstractions/fonts" = ''
+ # fonts.conf
+ r ${supportFontsConf},
+ r ${latestPkg.out}/etc/fonts/fonts.conf,
+
+ # fontconfig default config files
+ r ${supportPkg.out}/etc/fonts/conf.d/*.conf,
+ r ${latestPkg.out}/etc/fonts/conf.d/*.conf,
+
+ # 00-nixos-cache.conf
+ r ${cacheConfSupport},
+ r ${cacheConfLatest},
+
+ # 10-nixos-rendering.conf
+ r ${renderConf},
+
+ # local.conf (indirect priority 51)
+ ${optionalString (cfg.localConf != "") ''
+ r ${localConf},
+ ''}
+
+ # 52-nixos-default-fonts.conf
+ r ${defaultFontsConf},
+
+ # 53-no-bitmaps.conf
+ r ${rejectBitmaps},
+
+ ${optionalString (!cfg.allowType1) ''
+ # 53-nixos-reject-type1.conf
+ r ${rejectType1},
+ ''}
+ '';
})
(mkIf (cfg.enable && !cfg.penultimate.enable) {
fonts.fontconfig.confPackages = [ confPkg ];
diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix
index 6c479e070e2..e6d9467f296 100644
--- a/nixos/modules/security/apparmor-suid.nix
+++ b/nixos/modules/security/apparmor-suid.nix
@@ -21,9 +21,9 @@ with lib;
};
config = mkIf (cfg.confineSUIDApplications) {
- security.apparmor.profiles = [ (pkgs.writeText "ping" ''
+ security.apparmor.policies."bin/ping".profile = ''
#include
- /run/wrappers/bin/ping {
+ /run/wrappers/wrappers.*/ping {
#include
#include
#include
@@ -32,10 +32,19 @@ with lib;
capability setuid,
network inet raw,
- ${pkgs.stdenv.cc.libc.out}/lib/*.so mr,
- ${pkgs.libcap.lib}/lib/libcap.so* mr,
- ${pkgs.attr.out}/lib/libattr.so* mr,
+ ${getLib pkgs.stdenv.cc.cc}/lib/*.so* mr,
+ ${getLib pkgs.stdenv.cc.libc}/lib/*.so* mr,
+ ${getLib pkgs.stdenv.cc.libc}/lib/gconv/gconv-modules r,
+ ${getLib pkgs.glibcLocales}/lib/locale/locale-archive r,
+ ${getLib pkgs.attr.out}/lib/libattr.so* mr,
+ ${getLib pkgs.libcap.lib}/lib/libcap.so* mr,
+ ${getLib pkgs.libcap_ng}/lib/libcap-ng.so* mr,
+ ${getLib pkgs.libidn2}/lib/libidn2.so* mr,
+ ${getLib pkgs.libunistring}/lib/libunistring.so* mr,
+ ${getLib pkgs.nettle}/lib/libnettle.so* mr,
+ #@{PROC}/@{pid}/environ r,
+ /run/wrappers/wrappers.*/ping.real r,
${pkgs.iputils}/bin/ping mixr,
#/etc/modules.conf r,
@@ -43,7 +52,7 @@ with lib;
## Site-specific additions and overrides. See local/README for details.
##include
}
- '') ];
+ '';
};
}
diff --git a/nixos/modules/security/apparmor.nix b/nixos/modules/security/apparmor.nix
index cfc65b347bc..f9abb18afd2 100644
--- a/nixos/modules/security/apparmor.nix
+++ b/nixos/modules/security/apparmor.nix
@@ -1,59 +1,190 @@
{ config, lib, pkgs, ... }:
let
- inherit (lib) mkIf mkOption types concatMapStrings;
+ inherit (builtins) head match readFile;
+ inherit (lib) types;
+ inherit (config.environment) etc;
cfg = config.security.apparmor;
+ mkDisableOption = name: lib.mkEnableOption name // {
+ default = true;
+ example = false;
+ };
in
{
- options = {
- security.apparmor = {
- enable = mkOption {
- type = types.bool;
- default = false;
- description = "Enable the AppArmor Mandatory Access Control system.";
- };
- profiles = mkOption {
- type = types.listOf types.path;
- default = [];
- description = "List of files containing AppArmor profiles.";
- };
- packages = mkOption {
- type = types.listOf types.package;
- default = [];
- description = "List of packages to be added to apparmor's include path";
- };
- };
- };
+ imports = [
+ (lib.mkRemovedOptionModule [ "security" "apparmor" "profiles" ] "Please use the new option: `security.apparmor.policies'.")
+ apparmor/profiles.nix
+ ];
+ options = {
+ security.apparmor = {
+ enable = lib.mkEnableOption "the AppArmor Mandatory Access Control system";
+ policies = lib.mkOption {
+ description = ''
+ AppArmor policies.
+ '';
+ type = types.attrsOf (types.submodule ({ name, config, ... }: {
+ options = {
+ enable = mkDisableOption "loading of the profile into the kernel";
+ enforce = mkDisableOption "enforcing of the policy or only complain in the logs";
+ profile = lib.mkOption {
+ description = "The policy of the profile.";
+ type = types.lines;
+ apply = pkgs.writeText name;
+ };
+ };
+ }));
+ default = {};
+ };
+ includes = lib.mkOption {
+ type = types.attrsOf types.lines;
+ default = [];
+ description = ''
+ List of paths to be added to AppArmor's searched paths
+ when resolving absolute #include directives.
+ '';
+ apply = lib.mapAttrs pkgs.writeText;
+ };
+ packages = lib.mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = "List of packages to be added to AppArmor's include path";
+ };
+ enableCache = lib.mkEnableOption ''caching of AppArmor policies
+ in /var/cache/apparmor/.
+ Beware that AppArmor policies almost always contain Nix store paths,
+ and thus produce at each change of these paths
+ a new cached version accumulating in the cache.
+ '';
+ killUnconfinedConfinables = mkDisableOption ''killing of processes
+ which have an AppArmor profile enabled
+ (in policies)
+ but are not confined (because AppArmor can only confine new processes).
+ '';
+ };
+ };
- config = mkIf cfg.enable {
- environment.systemPackages = [ pkgs.apparmor-utils ];
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.apparmor-utils ];
+ environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" (
+ lib.mapAttrsToList (name: p: {inherit name; path=p.profile;}) cfg.policies ++
+ lib.mapAttrsToList (name: path: {inherit name path;}) cfg.includes
+ );
+ environment.etc."apparmor/parser.conf".text = ''
+ ${if cfg.enableCache then "write-cache" else "skip-cache"}
+ cache-loc /var/cache/apparmor
+ Include /etc/apparmor.d
+ '' +
+ lib.concatMapStrings (p: "Include ${p}/etc/apparmor.d\n") cfg.packages;
+ environment.etc."apparmor/logprof.conf".text = ''
+ [settings]
+ profiledir = /etc/apparmor.d
+ inactive_profiledir = ${pkgs.apparmor-profiles}/share/apparmor/extra-profiles
+ logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages
- boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
+ parser = ${pkgs.apparmor-parser}/bin/apparmor_parser
+ ldd = ${pkgs.glibc.bin}/bin/ldd
+ logger = ${pkgs.utillinux}/bin/logger
- systemd.services.apparmor = let
- paths = concatMapStrings (s: " -I ${s}/etc/apparmor.d")
- ([ pkgs.apparmor-profiles ] ++ cfg.packages);
- in {
- after = [ "local-fs.target" ];
- before = [ "sysinit.target" ];
- wantedBy = [ "multi-user.target" ];
- unitConfig = {
- DefaultDependencies = "no";
- };
- serviceConfig = {
- Type = "oneshot";
- RemainAfterExit = "yes";
- ExecStart = map (p:
- ''${pkgs.apparmor-parser}/bin/apparmor_parser -rKv ${paths} "${p}"''
- ) cfg.profiles;
- ExecStop = map (p:
- ''${pkgs.apparmor-parser}/bin/apparmor_parser -Rv "${p}"''
- ) cfg.profiles;
- ExecReload = map (p:
- ''${pkgs.apparmor-parser}/bin/apparmor_parser --reload ${paths} "${p}"''
- ) cfg.profiles;
- };
- };
- };
+ # 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
+ ${pkgs.bashInteractive}/bin/sh = icnu
+ ${pkgs.bashInteractive}/bin/bash = icnu
+ '' + head (match "^.*\\[qualifiers](.*)" # Drop the original [settings] section.
+ (readFile "${pkgs.apparmor-utils}/etc/apparmor/logprof.conf"));
+
+ 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
+ ];
+ unitConfig = {
+ Description="Load AppArmor policies";
+ DefaultDependencies = "no";
+ ConditionSecurity = "apparmor";
+ };
+ # Reloading instead of restarting enables to load new AppArmor profiles
+ # without necessarily restarting all services which have Requires=apparmor.service
+ # It works by:
+ # - Adding or replacing into the kernel profiles enabled in cfg.policies
+ # (because AppArmor can do that without stopping the processes already confined).
+ # - Removing from the kernel any profile whose name is not
+ # one of the names within the content of the profiles in cfg.policies.
+ # - Killing the processes which are unconfined but now have a profile loaded
+ # (because AppArmor can only confine new processes).
+ reloadIfChanged = true;
+ # Avoid searchs in /usr/share/locale/
+ environment.LANG="C";
+ serviceConfig = let
+ enabledPolicies = lib.attrValues (lib.filterAttrs (n: p: p.enable) cfg.policies);
+ removeDisabledProfiles = pkgs.writeShellScript "apparmor-remove" ''
+ set -eux
+
+ enabledProfiles=$(mktemp)
+ loadedProfiles=$(mktemp)
+ trap "rm -f $enabledProfiles $loadedProfiles" EXIT
+
+ ${pkgs.apparmor-parser}/bin/apparmor_parser --names /dev/null ${
+ lib.concatMapStrings (p: "\\\n "+p.profile) enabledPolicies} |
+ sort -u >"$enabledProfiles"
+
+ sed -e "s/ (\(enforce\|complain\))$//" /sys/kernel/security/apparmor/profiles |
+ sort -u >"$loadedProfiles"
+
+ comm -23 "$loadedProfiles" "$enabledProfiles" |
+ while IFS=$'\n\r' read -r profile
+ do printf %s "$profile" >/sys/kernel/security/apparmor/.remove
+ done
+ '';
+ killUnconfinedConfinables = pkgs.writeShellScript "apparmor-kill" ''
+ set -eux
+ ${pkgs.apparmor-utils}/bin/aa-status --json |
+ ${pkgs.jq}/bin/jq --raw-output '.processes | .[] | .[] | select (.status == "unconfined") | .pid' |
+ xargs --verbose --no-run-if-empty --delimiter='\n' \
+ kill
+ '';
+ commonOpts = p: "--verbose --show-cache ${lib.optionalString (!p.enforce) "--complain "}${p.profile}";
+ in {
+ Type = "oneshot";
+ RemainAfterExit = "yes";
+ ExecStartPre = "${pkgs.apparmor-utils}/bin/aa-teardown";
+ ExecStart = map (p: "${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}") enabledPolicies;
+ ExecStartPost = lib.optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
+ ExecReload =
+ map (p: "${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}") enabledPolicies ++
+ [ removeDisabledProfiles ] ++
+ lib.optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
+ ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown";
+ CacheDirectory = [ "apparmor" ];
+ CacheDirectoryMode = "0700";
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ julm ];
}
diff --git a/nixos/modules/security/apparmor/profiles.nix b/nixos/modules/security/apparmor/profiles.nix
new file mode 100644
index 00000000000..7e33e630798
--- /dev/null
+++ b/nixos/modules/security/apparmor/profiles.nix
@@ -0,0 +1,333 @@
+{ config, lib, pkgs, ... }:
+let
+ inherit (builtins) attrNames hasAttr isAttrs;
+ inherit (lib) getLib;
+ inherit (config.environment) etc;
+ etcRule = arg:
+ let go = {path ? null, mode ? "r", trail ? ""}:
+ lib.optionalString (hasAttr path etc)
+ "${mode} ${config.environment.etc."${path}".source}${trail},";
+ in if isAttrs arg
+ then go arg
+ else go {path=arg;};
+in
+{
+config.security.apparmor.packages = [ pkgs.apparmor-profiles ];
+# FIXME: most of the etcRule calls below have been
+# written systematically by converting from apparmor-profiles's profiles
+# without testing nor deep understanding of their uses,
+# and thus may need more rules or can have less rules;
+# this remains to me determined case by case,
+# some may even be completely useless.
+config.security.apparmor.includes = {
+ # This one is included by
+ # which is usualy included before any profile.
+ "abstractions/tunables/alias" = ''
+ alias /bin -> /run/current-system/sw/bin,
+ # Unfortunately /etc is mainly built using symlinks,
+ # thus aliasing does not work.
+ #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/audio" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/audio
+ ${etcRule "asound.conf"}
+ ${etcRule "esound/esd.conf"}
+ ${etcRule "libao.conf"}
+ ${etcRule {path="pulse"; trail="/";}}
+ ${etcRule {path="pulse"; trail="/**";}}
+ ${etcRule {path="sound"; trail="/";}}
+ ${etcRule {path="sound"; trail="/**";}}
+ ${etcRule {path="alsa/conf.d"; trail="/";}}
+ ${etcRule {path="alsa/conf.d"; trail="/*";}}
+ ${etcRule "openal/alsoft.conf"}
+ ${etcRule "wildmidi/wildmidi.conf"}
+ '';
+ # FIXME: security.pam configures more .so than allowed here,
+ # but has many tests to decide what .so to use,
+ # so it would be simpler to let security.pam add those .so
+ # to the present security.apparmor.includes."abstractions/authentication"
+ "abstractions/authentication" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/authentication
+ ${etcRule "nologin"}
+ ${lib.concatMapStringsSep "\n"
+ (name: "r ${etc."pam.d/${name}".source},")
+ (attrNames config.security.pam.services)}
+ mr ${getLib pkgs.pam}/lib/security/pam_filter/*,
+ mr ${getLib pkgs.pam}/lib/security/pam_*.so,
+ r ${getLib pkgs.pam}/lib/security/,
+ ${etcRule "securetty"}
+ ${etcRule {path="security"; trail="/*";}}
+ ${etcRule "shadow"}
+ ${etcRule "gshadow"}
+ ${etcRule "pwdb.conf"}
+ ${etcRule "default/passwd"}
+ ${etcRule "login.defs"}
+ '';
+ "abstractions/base" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/base
+ r ${pkgs.stdenv.cc.libc}/share/locale/**,
+ r ${pkgs.stdenv.cc.libc}/share/locale.alias,
+ ${etcRule "localtime"}
+ r /etc/ld-nix.so.preload,
+ ${etcRule "ld-nix.so.preload"}
+ ${lib.concatMapStrings (p: lib.optionalString (p != "") "mr ${p},\n")
+ (lib.splitString "\n" etc."ld-nix.so.preload".text)
+ # TODO: avoid this line splitting by nixifying ld-nix.so.preload as a list or attrset,
+ # and make services.config.malloc use it.
+ }
+ r ${pkgs.tzdata}/share/zoneinfo/**,
+ r ${pkgs.stdenv.cc.libc}/share/i18n/**,
+ '';
+ "abstractions/bash" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/bash
+ # system-wide bash configuration
+ ${etcRule "profile.dos"}
+ ${etcRule "profile"}
+ ${etcRule "profile.d"}
+ ${etcRule {path="profile.d"; trail="/*";}}
+ ${etcRule "bashrc"}
+ ${etcRule "bash.bashrc"}
+ ${etcRule "bash.bashrc.local"}
+ ${etcRule "bash_completion"}
+ ${etcRule "bash_completion.d"}
+ ${etcRule {path="bash_completion.d"; trail="/*";}}
+ # bash relies on system-wide readline configuration
+ ${etcRule "inputrc"}
+ # bash inspects filesystems at startup
+ # and /etc/mtab is linked to /proc/mounts
+ @{PROC}/mounts
+
+ # run out of /etc/bash.bashrc
+ ${etcRule "DIR_COLORS"}
+ '';
+ "abstractions/cups-client" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/cpus-client
+ ${etcRule "cups/cups-client.conf"}
+ '';
+ "abstractions/consoles" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/consoles
+ '';
+ "abstractions/dbus-session-strict" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dbus-session-strict
+ ${etcRule "machine-id"}
+ '';
+ "abstractions/dconf" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dconf
+ ${etcRule {path="dconf"; trail="/**";}}
+ '';
+ "abstractions/dri-common" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dri-common
+ ${etcRule "drirc"}
+ '';
+ # The config.fonts.fontconfig NixOS module adds many files to /etc/fonts/
+ # by symlinking them but without exporting them outside of its NixOS module,
+ # those are therefore added there to this "abstractions/fonts".
+ "abstractions/fonts" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/fonts
+ ${etcRule {path="fonts"; trail="/**";}}
+ '';
+ "abstractions/gnome" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/gnome
+ ${etcRule {path="gnome"; trail="/gtkrc*";}}
+ ${etcRule {path="gtk"; trail="/*";}}
+ ${etcRule {path="gtk-2.0"; trail="/*";}}
+ ${etcRule {path="gtk-3.0"; trail="/*";}}
+ ${etcRule "orbitrc"}
+ #include
+ ${etcRule {path="pango"; trail="/*";}}
+ ${etcRule {path="/etc/gnome-vfs-2.0"; trail="/modules/";}}
+ ${etcRule {path="/etc/gnome-vfs-2.0"; trail="/modules/*";}}
+ ${etcRule "papersize"}
+ ${etcRule {path="cups"; trail="/lpoptions";}}
+ ${etcRule {path="gnome"; trail="/defaults.list";}}
+ ${etcRule {path="xdg"; trail="/{,*-}mimeapps.list";}}
+ ${etcRule "xdg/mimeapps.list"}
+ '';
+ "abstractions/kde" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/kde
+ ${etcRule {path="qt3"; trail="/kstylerc";}}
+ ${etcRule {path="qt3"; trail="/qt_plugins_3.3rc";}}
+ ${etcRule {path="qt3"; trail="/qtrc";}}
+ ${etcRule "kderc"}
+ ${etcRule {path="kde3"; trail="/*";}}
+ ${etcRule "kde4rc"}
+ ${etcRule {path="xdg"; trail="/kdeglobals";}}
+ ${etcRule {path="xdg"; trail="/Trolltech.conf";}}
+ '';
+ "abstractions/kerberosclient" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/kerberosclient
+ ${etcRule {path="krb5.keytab"; mode="rk";}}
+ ${etcRule "krb5.conf"}
+ ${etcRule "krb5.conf.d"}
+ ${etcRule {path="krb5.conf.d"; trail="/*";}}
+
+ # config files found via strings on libs
+ ${etcRule "krb.conf"}
+ ${etcRule "krb.realms"}
+ ${etcRule "srvtab"}
+ '';
+ "abstractions/ldapclient" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/ldapclient
+ ${etcRule "ldap.conf"}
+ ${etcRule "ldap.secret"}
+ ${etcRule {path="openldap"; trail="/*";}}
+ ${etcRule {path="openldap"; trail="/cacerts/*";}}
+ ${etcRule {path="sasl2"; trail="/*";}}
+ '';
+ "abstractions/likewise" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/likewise
+ '';
+ "abstractions/mdns" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/mdns
+ ${etcRule "nss_mdns.conf"}
+ '';
+ "abstractions/nameservice" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nameservice
+
+ # Many programs wish to perform nameservice-like operations, such as
+ # looking up users by name or id, groups by name or id, hosts by name
+ # or IP, etc. These operations may be performed through files, dns,
+ # NIS, NIS+, LDAP, hesiod, wins, etc. Allow them all here.
+ ${etcRule "group"}
+ ${etcRule "host.conf"}
+ ${etcRule "hosts"}
+ ${etcRule "nsswitch.conf"}
+ ${etcRule "gai.conf"}
+ ${etcRule "passwd"}
+ ${etcRule "protocols"}
+
+ # libtirpc (used for NIS/YP login) needs this
+ ${etcRule "netconfig"}
+
+ ${etcRule "resolv.conf"}
+
+ ${etcRule {path="samba"; trail="/lmhosts";}}
+ ${etcRule "services"}
+
+ ${etcRule "default/nss"}
+
+ # libnl-3-200 via libnss-gw-name
+ ${etcRule {path="libnl"; trail="/classid";}}
+ ${etcRule {path="libnl-3"; trail="/classid";}}
+
+ mr ${getLib pkgs.nss}/lib/libnss_*.so*,
+ mr ${getLib pkgs.nss}/lib64/libnss_*.so*,
+ '';
+ "abstractions/nis" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nis
+ '';
+ "abstractions/nvidia" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nvidia
+ ${etcRule "vdpau_wrapper.cfg"}
+ '';
+ "abstractions/opencl-common" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/opencl-common
+ ${etcRule {path="OpenCL"; trail="/**";}}
+ '';
+ "abstractions/opencl-mesa" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/opencl-mesa
+ ${etcRule "default/drirc"}
+ '';
+ "abstractions/openssl" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/openssl
+ ${etcRule {path="ssl"; trail="/openssl.cnf";}}
+ '';
+ "abstractions/p11-kit" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/p11-kit
+ ${etcRule {path="pkcs11"; trail="/";}}
+ ${etcRule {path="pkcs11"; trail="/pkcs11.conf";}}
+ ${etcRule {path="pkcs11"; trail="/modules/";}}
+ ${etcRule {path="pkcs11"; trail="/modules/*";}}
+ '';
+ "abstractions/perl" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/perl
+ ${etcRule {path="perl"; trail="/**";}}
+ '';
+ "abstractions/php" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/php
+ ${etcRule {path="php"; trail="/**/";}}
+ ${etcRule {path="php5"; trail="/**/";}}
+ ${etcRule {path="php7"; trail="/**/";}}
+ ${etcRule {path="php"; trail="/**.ini";}}
+ ${etcRule {path="php5"; trail="/**.ini";}}
+ ${etcRule {path="php7"; trail="/**.ini";}}
+ '';
+ "abstractions/postfix-common" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/postfix-common
+ ${etcRule "mailname"}
+ ${etcRule {path="postfix"; trail="/*.cf";}}
+ ${etcRule "postfix/main.cf"}
+ ${etcRule "postfix/master.cf"}
+ '';
+ "abstractions/python" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/python
+ ${etcRule {path="python2.4"; trail="/**";}}
+ ${etcRule {path="python2.5"; trail="/**";}}
+ ${etcRule {path="python2.6"; trail="/**";}}
+ ${etcRule {path="python2.7"; trail="/**";}}
+ ${etcRule {path="python3.0"; trail="/**";}}
+ ${etcRule {path="python3.1"; trail="/**";}}
+ ${etcRule {path="python3.2"; trail="/**";}}
+ ${etcRule {path="python3.3"; trail="/**";}}
+ ${etcRule {path="python3.4"; trail="/**";}}
+ ${etcRule {path="python3.5"; trail="/**";}}
+ ${etcRule {path="python3.6"; trail="/**";}}
+ ${etcRule {path="python3.7"; trail="/**";}}
+ ${etcRule {path="python3.8"; trail="/**";}}
+ ${etcRule {path="python3.9"; trail="/**";}}
+ '';
+ "abstractions/qt5" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/qt5
+ ${etcRule {path="xdg"; trail="/QtProject/qtlogging.ini";}}
+ ${etcRule {path="xdg/QtProject"; trail="/qtlogging.ini";}}
+ ${etcRule "xdg/QtProject/qtlogging.ini"}
+ '';
+ "abstractions/samba" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/samba
+ ${etcRule {path="samba"; trail="/*";}}
+ '';
+ "abstractions/ssl_certs" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/ssl_certs
+ ${etcRule "ssl/certs/ca-certificates.crt"}
+ ${etcRule "ssl/certs/ca-bundle.crt"}
+ ${etcRule "pki/tls/certs/ca-bundle.crt"}
+
+ ${etcRule {path="ssl/trust"; trail="/";}}
+ ${etcRule {path="ssl/trust"; trail="/*";}}
+ ${etcRule {path="ssl/trust/anchors"; trail="/";}}
+ ${etcRule {path="ssl/trust/anchors"; trail="/**";}}
+ ${etcRule {path="pki/trust"; trail="/";}}
+ ${etcRule {path="pki/trust"; trail="/*";}}
+ ${etcRule {path="pki/trust/anchors"; trail="/";}}
+ ${etcRule {path="pki/trust/anchors"; trail="/**";}}
+
+ # security.acme NixOS module
+ r /var/lib/acme/*/cert.pem,
+ r /var/lib/acme/*/chain.pem,
+ r /var/lib/acme/*/fullchain.pem,
+ '';
+ "abstractions/ssl_keys" = ''
+ # security.acme NixOS module
+ r /var/lib/acme/*/full.pem,
+ r /var/lib/acme/*/key.pem,
+ '';
+ "abstractions/vulkan" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/vulkan
+ ${etcRule {path="vulkan/icd.d"; trail="/";}}
+ ${etcRule {path="vulkan/icd.d"; trail="/*.json";}}
+ '';
+ "abstractions/winbind" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/winbind
+ ${etcRule {path="samba"; trail="/smb.conf";}}
+ ${etcRule {path="samba"; trail="/dhcp.conf";}}
+ '';
+ "abstractions/X" = ''
+ #include ${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/X
+ ${etcRule {path="X11/cursors"; trail="/";}}
+ ${etcRule {path="X11/cursors"; trail="/**";}}
+ '';
+};
+}
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 688344852ae..339dbd4cb3a 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -789,6 +789,57 @@ in
runuser-l = { rootOK = true; unixAuth = false; };
};
+ security.apparmor.includes."abstractions/pam" = let
+ isEnabled = test: fold or false (map test (attrValues config.security.pam.services));
+ in ''
+ ${optionalString use_ldap
+ "mr ${pam_ldap}/lib/security/pam_ldap.so,"}
+ ${optionalString config.services.sssd.enable
+ "mr ${pkgs.sssd}/lib/security/pam_sss.so,"}
+ ${optionalString config.krb5.enable ''
+ mr ${pam_krb5}/lib/security/pam_krb5.so,
+ mr ${pam_ccreds}/lib/security/pam_ccreds.so,
+ ''}
+ ${optionalString (isEnabled (cfg: cfg.googleOsLoginAccountVerification)) ''
+ mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so,
+ mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_admin.so,
+ ''}
+ ${optionalString (isEnabled (cfg: cfg.googleOsLoginAuthentication))
+ "mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so,"}
+ ${optionalString (config.security.pam.enableSSHAgentAuth && isEnabled (cfg: cfg.sshAgentAuth))
+ "mr ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so,"}
+ ${optionalString (isEnabled (cfg: cfg.fprintAuth))
+ "mr ${pkgs.fprintd}/lib/security/pam_fprintd.so,"}
+ ${optionalString (isEnabled (cfg: cfg.u2fAuth))
+ "mr ${pkgs.pam_u2f}/lib/security/pam_u2f.so,"}
+ ${optionalString (isEnabled (cfg: cfg.usbAuth))
+ "mr ${pkgs.pam_usb}/lib/security/pam_usb.so,"}
+ ${optionalString (isEnabled (cfg: cfg.oathAuth))
+ "mr ${pkgs.oathToolkit}/lib/security/pam_oath.so,"}
+ ${optionalString (isEnabled (cfg: cfg.yubicoAuth))
+ "mr ${pkgs.yubico-pam}/lib/security/pam_yubico.so,"}
+ ${optionalString (isEnabled (cfg: cfg.duoSecurity.enable))
+ "mr ${pkgs.duo-unix}/lib/security/pam_duo.so,"}
+ ${optionalString (isEnabled (cfg: cfg.otpwAuth))
+ "mr ${pkgs.otpw}/lib/security/pam_otpw.so,"}
+ ${optionalString config.security.pam.enableEcryptfs
+ "mr ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so,"}
+ ${optionalString (isEnabled (cfg: cfg.pamMount))
+ "mr ${pkgs.pam_mount}/lib/security/pam_mount.so,"}
+ ${optionalString config.services.samba.syncPasswordsByPam
+ "mr ${pkgs.samba}/lib/security/pam_smbpass.so,"}
+ ${optionalString (isEnabled (cfg: cfg.enableGnomeKeyring))
+ "mr ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so,"}
+ ${optionalString (isEnabled (cfg: cfg.startSession))
+ "mr ${pkgs.systemd}/lib/security/pam_systemd.so,"}
+ ${optionalString (isEnabled (cfg: cfg.enableAppArmor) && config.security.apparmor.enable)
+ "mr ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so,"}
+ ${optionalString (isEnabled (cfg: cfg.enableKwallet))
+ "mr ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so,"}
+ ${optionalString config.virtualisation.lxc.lxcfs.enable
+ "mr ${pkgs.lxc}/lib/security/pam_cgfs.so"}
+ '';
+
};
}
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index 1bfcf2de82f..6fe54000c0d 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -1,52 +1,50 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, options, ... }:
with lib;
let
cfg = config.services.transmission;
+ inherit (config.environment) etc;
apparmor = config.security.apparmor.enable;
-
- homeDir = cfg.home;
- downloadDirPermissions = cfg.downloadDirPermissions;
- downloadDir = "${homeDir}/Downloads";
- incompleteDir = "${homeDir}/.incomplete";
-
- settingsDir = "${homeDir}/config";
- settingsFile = pkgs.writeText "settings.json" (builtins.toJSON fullSettings);
-
- # for users in group "transmission" to have access to torrents
- fullSettings = { umask = 2; download-dir = downloadDir; incomplete-dir = incompleteDir; } // cfg.settings;
-
- preStart = pkgs.writeScript "transmission-pre-start" ''
- #!${pkgs.runtimeShell}
- set -ex
- cp -f ${settingsFile} ${settingsDir}/settings.json
- '';
+ rootDir = "/run/transmission";
+ homeDir = "/var/lib/transmission";
+ settingsDir = ".config/transmission-daemon";
+ downloadsDir = "Downloads";
+ incompleteDir = ".incomplete";
+ # TODO: switch to configGen.json once RFC0042 is implemented
+ settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
in
{
options = {
services.transmission = {
- enable = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether or not to enable the headless Transmission BitTorrent daemon.
+ enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
- Transmission daemon can be controlled via the RPC interface using
- transmission-remote or the WebUI (http://localhost:9091/ by default).
+ Transmission daemon can be controlled via the RPC interface using
+ transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
+ or other clients like stig or tremc.
- Torrents are downloaded to ${downloadDir} by default and are
- accessible to users in the "transmission" group.
- '';
- };
+ Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
+ accessible to users in the "transmission" group'';
- settings = mkOption {
+ settings = mkOption rec {
+ # TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented
type = types.attrs;
+ apply = recursiveUpdate default;
default =
{
- download-dir = downloadDir;
- incomplete-dir = incompleteDir;
+ download-dir = "${cfg.home}/${downloadsDir}";
+ incomplete-dir = "${cfg.home}/${incompleteDir}";
incomplete-dir-enabled = true;
+ peer-port = 51413;
+ peer-port-random-high = 65535;
+ peer-port-random-low = 49152;
+ peer-port-random-on-start = false;
+ rpc-bind-address = "127.0.0.1";
+ rpc-port = 9091;
+ script-torrent-done-enabled = false;
+ script-torrent-done-filename = "";
+ umask = 2; # 0o002 in decimal as expected by Transmission
+ utp-enabled = true;
};
example =
{
@@ -56,8 +54,9 @@ in
rpc-whitelist = "127.0.0.1,192.168.*.*";
};
description = ''
- Attribute set whos fields overwrites fields in settings.json (each
- time the service starts). String values must be quoted, integer and
+ Attribute set whose fields overwrites fields in
+ .config/transmission-daemon/settings.json
+ (each time the service starts). String values must be quoted, integer and
boolean values must not.
See https://github.com/transmission/transmission/wiki/Editing-Configuration-Files
@@ -70,22 +69,32 @@ in
default = "770";
example = "775";
description = ''
- The permissions to set for download-dir and incomplete-dir.
- They will be applied on every service start.
+ The permissions set by systemd.activationScripts.transmission-daemon
+ on the directories settings.download-dir
+ and settings.incomplete-dir.
+ Note that you may also want to change
+ settings.umask.
'';
};
port = mkOption {
- type = types.int;
- default = 9091;
- description = "TCP port number to run the RPC/web interface.";
+ type = types.port;
+ description = ''
+ TCP port number to run the RPC/web interface.
+
+ If instead you want to change the peer port,
+ use settings.peer-port
+ or settings.peer-port-random-on-start.
+ '';
};
home = mkOption {
type = types.path;
- default = "/var/lib/transmission";
+ default = homeDir;
description = ''
- The directory where transmission will create files.
+ The directory where Transmission will create ${settingsDir}.
+ as well as ${downloadsDir}/ unless settings.download-dir is changed,
+ and ${incompleteDir}/ unless settings.incomplete-dir is changed.
'';
};
@@ -100,32 +109,174 @@ in
default = "transmission";
description = "Group account under which Transmission runs.";
};
+
+ credentialsFile = mkOption {
+ type = types.path;
+ description = ''
+ Path to a JSON file to be merged with the settings.
+ Useful to merge a file which is better kept out of the Nix store
+ because it contains sensible data like settings.rpc-password.
+ '';
+ default = "/dev/null";
+ example = "/var/lib/secrets/transmission/settings.json";
+ };
+
+ openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
+
+ performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
+ to open many more connections at the same time.
+
+ Note that you may also want to increase
+ settings.peer-limit-global.
+ And be aware that these settings are quite aggressive
+ and might not suite your regular desktop use.
+ For instance, SSH sessions may time out more easily'';
};
};
config = mkIf cfg.enable {
+ # Note that using systemd.tmpfiles would not work here
+ # because it would fail when creating a directory
+ # with a different owner than its parent directory, by saying:
+ # Detected unsafe path transition /home/foo → /home/foo/Downloads during canonicalization of /home/foo/Downloads.
+ # When /home/foo is not owned by cfg.user.
+ # Note also that using an ExecStartPre= wouldn't work either
+ # because BindPaths= needs these directories before.
+ system.activationScripts.transmission-daemon = ''
+ install -D -d -m 700 '${cfg.home}/${settingsDir}'
+ chown -R '${cfg.user}:${cfg.group}' ${cfg.home}/${settingsDir}
+ install -D -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
+ '' + optionalString cfg.settings.incomplete-dir-enabled ''
+ install -D -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
+ '';
+
+ assertions = [
+ { assertion = builtins.match "^/.*" cfg.home != null;
+ message = "`services.transmission.home' must be an absolute path.";
+ }
+ { assertion = types.path.check cfg.settings.download-dir;
+ message = "`services.transmission.settings.download-dir' must be an absolute path.";
+ }
+ { assertion = types.path.check cfg.settings.incomplete-dir;
+ message = "`services.transmission.settings.incomplete-dir' must be an absolute path.";
+ }
+ { assertion = cfg.settings.script-torrent-done-filename == "" || types.path.check cfg.settings.script-torrent-done-filename;
+ message = "`services.transmission.settings.script-torrent-done-filename' must be an absolute path.";
+ }
+ { assertion = types.port.check cfg.settings.rpc-port;
+ message = "${toString cfg.settings.rpc-port} is not a valid port number for `services.transmission.settings.rpc-port`.";
+ }
+ # In case both port and settings.rpc-port are explicitely defined: they must be the same.
+ { assertion = !options.services.transmission.port.isDefined || cfg.port == cfg.settings.rpc-port;
+ message = "`services.transmission.port' is not equal to `services.transmission.settings.rpc-port'";
+ }
+ ];
+
+ services.transmission.settings =
+ optionalAttrs options.services.transmission.port.isDefined { rpc-port = cfg.port; };
+
systemd.tmpfiles.rules = [
- "d '${homeDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
- "d '${settingsDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
- "d '${fullSettings.download-dir}' '${downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"
- "d '${fullSettings.incomplete-dir}' '${downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"
+ # SETGUID is used to propagate cfg.group on intermediate paths
+ # created by BindPaths=/BindReadOnlyPaths=
+ "d ${rootDir} 2700 ${cfg.user} ${cfg.group} - -"
];
systemd.services.transmission = {
description = "Transmission BitTorrent Service";
after = [ "network.target" ] ++ optional apparmor "apparmor.service";
- requires = mkIf apparmor [ "apparmor.service" ];
+ requires = optional apparmor "apparmor.service";
wantedBy = [ "multi-user.target" ];
+ environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
- # 1) Only the "transmission" user and group have access to torrents.
- # 2) Optionally update/force specific fields into the configuration file.
- serviceConfig.ExecStartPre = preStart;
- serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port} --config-dir ${settingsDir}";
- serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
- serviceConfig.User = cfg.user;
- serviceConfig.Group = cfg.group;
- # NOTE: transmission has an internal umask that also must be set (in settings.json)
- serviceConfig.UMask = "0002";
+ serviceConfig = {
+ # Use "+" because credentialsFile may not be accessible to User= or Group=.
+ ExecStartPre = "+" + pkgs.writeShellScript "transmission-prestart" ''
+ set -eux
+ ${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' |
+ install -D -m 600 -o '${cfg.user}' -g '${cfg.group}' /dev/stdin \
+ '${cfg.home}/${settingsDir}/settings.json'
+ '';
+ ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = cfg.user;
+ Group = cfg.group;
+ # This allows to use the same paths download-dir/incomplete-dir
+ # (which appear in user's interfaces)
+ # while not requiring to give cfg.user access to their parent directories
+ # by using BindPaths=/BindReadOnlyPaths=
+ # Note that TemporaryFileSystem= could be used but not without adding
+ # some BindPaths=/BindReadOnlyPaths= only needed for ExecStartPre=
+ # because RootDirectoryStartOnly=true would not apply.
+ RootDirectory = rootDir;
+ RootDirectoryStartOnly = true;
+ MountAPIVFS = true;
+ # This is for BindPaths= and BindReadOnlyPaths=
+ # to allow Group= to traverse (g+x) directories
+ # they create in RootDirectory= (which has g+s from systemd.tmpfiles).
+ UMask = "0067";
+ BindPaths =
+ [ "${cfg.home}/${settingsDir}"
+ cfg.settings.download-dir
+ ] ++
+ optional cfg.settings.incomplete-dir-enabled
+ cfg.settings.incomplete-dir ++
+ optional (cfg.settings.script-torrent-done-enabled &&
+ cfg.settings.script-torrent-done-filename != "")
+ cfg.settings.script-torrent-done-filename;
+ BindReadOnlyPaths = [
+ # No confinement done of /nix/store here like in systemd-confinement.nix,
+ # an AppArmor profile is provided to get a confinement based upon paths and rights.
+ builtins.storeDir
+ "-/etc/hosts"
+ "-/etc/ld-nix.so.preload"
+ "-/etc/localtime"
+ ];
+ # The following options are only for optimizing:
+ # systemd-analyze security transmission
+ AmbientCapabilities = "";
+ CapabilityBoundingSet = "";
+ # ProtectClock= adds DeviceAllow=char-rtc r
+ DeviceAllow = "";
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ NoNewPrivileges = true;
+ PrivateDevices = true;
+ PrivateMounts = true;
+ PrivateNetwork = false;
+ PrivateTmp = true;
+ PrivateUsers = true;
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ # ProtectHome=true would not allow BindPaths= to work accross /home,
+ # and ProtectHome=tmpfs would break statfs(),
+ # preventing transmission-daemon to report the available free space.
+ # However, RootDirectory= is used, so this is not a security concern
+ # since there would be nothing in /home but any BindPaths= wanted by the user.
+ ProtectHome = "read-only";
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ProtectSystem = "strict";
+ RemoveIPC = true;
+ # AF_UNIX may become usable one day:
+ # https://github.com/transmission/transmission/issues/441
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ SystemCallFilter = [
+ "@system-service"
+ # Groups in @system-service which do not contain a syscall
+ # listed by perf -e 'syscalls:sys_enter_*' transmission-daemon -f
+ # in tests, and seem likely not necessary for transmission-daemon.
+ "~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" "~@setuid" "~@timer"
+ # In the @privileged group, but reached when querying infos through RPC (eg. with stig).
+ "quotactl"
+ ];
+ SystemCallArchitectures = "native";
+ SystemCallErrorNumber = "EPERM";
+ };
};
# It's useful to have transmission in path, e.g. for remote control
@@ -133,70 +284,135 @@ in
users.users = optionalAttrs (cfg.user == "transmission") ({
transmission = {
- name = "transmission";
group = cfg.group;
uid = config.ids.uids.transmission;
description = "Transmission BitTorrent user";
- home = homeDir;
- createHome = true;
+ home = cfg.home;
};
});
users.groups = optionalAttrs (cfg.group == "transmission") ({
transmission = {
- name = "transmission";
gid = config.ids.gids.transmission;
};
});
- # AppArmor profile
- security.apparmor.profiles = mkIf apparmor [
- (pkgs.writeText "apparmor-transmission-daemon" ''
- #include
+ networking.firewall = mkIf cfg.openFirewall (
+ if cfg.settings.peer-port-random-on-start
+ then
+ { allowedTCPPortRanges =
+ [ { from = cfg.settings.peer-port-random-low;
+ to = cfg.settings.peer-port-random-high;
+ }
+ ];
+ allowedUDPPortRanges =
+ [ { from = cfg.settings.peer-port-random-low;
+ to = cfg.settings.peer-port-random-high;
+ }
+ ];
+ }
+ else
+ { allowedTCPPorts = [ cfg.settings.peer-port ];
+ allowedUDPPorts = [ cfg.settings.peer-port ];
+ }
+ );
+
+ boot.kernel.sysctl =
+ # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
+ # and thus expects large kernel buffers for the UDP socket,
+ # at least up to the values hardcoded here:
+ # https://trac.transmissionbt.com/browser/trunk/libtransmission/tr-udp.c?rev=11956.
+ mkIf cfg.settings.utp-enabled {
+ "net.core.rmem_max" = mkDefault "4194304"; # 4MB
+ "net.core.wmem_max" = mkDefault "1048576"; # 1MB
+ } // mkIf cfg.performanceNetParameters {
+ # Increase the number of available source (local) TCP and UDP ports to 49151.
+ # Usual default is 32768 60999, ie. 28231 ports.
+ # Find out your current usage with: ss -s
+ "net.ipv4.ip_local_port_range" = "16384 65535";
+ # Timeout faster generic TCP states.
+ # Usual default is 600.
+ # Find out your current usage with: watch -n 1 netstat -nptuo
+ "net.netfilter.nf_conntrack_generic_timeout" = 60;
+ # Timeout faster established but inactive connections.
+ # Usual default is 432000.
+ "net.netfilter.nf_conntrack_tcp_timeout_established" = 600;
+ # Clear immediately TCP states after timeout.
+ # Usual default is 120.
+ "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = 1;
+ # Increase the number of trackable connections.
+ # Usual default is 262144.
+ # Find out your current usage with: conntrack -C
+ "net.netfilter.nf_conntrack_max" = 1048576;
+ };
+
+ security.apparmor.policies."bin/transmission-daemon".profile = ''
+ include
${pkgs.transmission}/bin/transmission-daemon {
- #include
- #include
+ include
+ include
- ${getLib pkgs.glibc}/lib/*.so mr,
- ${getLib pkgs.libevent}/lib/libevent*.so* mr,
- ${getLib pkgs.curl}/lib/libcurl*.so* mr,
- ${getLib pkgs.openssl}/lib/libssl*.so* mr,
- ${getLib pkgs.openssl}/lib/libcrypto*.so* mr,
- ${getLib pkgs.zlib}/lib/libz*.so* mr,
- ${getLib pkgs.libssh2}/lib/libssh2*.so* mr,
- ${getLib pkgs.systemd}/lib/libsystemd*.so* mr,
- ${getLib pkgs.xz}/lib/liblzma*.so* mr,
- ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so* mr,
- ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so* mr,
- ${getLib pkgs.nghttp2}/lib/libnghttp2*.so* mr,
- ${getLib pkgs.c-ares}/lib/libcares*.so* mr,
- ${getLib pkgs.libcap}/lib/libcap*.so* mr,
- ${getLib pkgs.attr}/lib/libattr*.so* mr,
- ${getLib pkgs.lz4}/lib/liblz4*.so* mr,
- ${getLib pkgs.libkrb5}/lib/lib*.so* mr,
- ${getLib pkgs.keyutils}/lib/libkeyutils*.so* mr,
- ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
- ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
- ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
- ${getLib pkgs.gcc.cc.lib}/lib/libstdc++.so.* mr,
- ${getLib pkgs.gcc.cc.lib}/lib/libgcc_s.so.* mr,
+ mr ${getLib pkgs.stdenv.cc.cc}/lib/*.so*,
+ mr ${getLib pkgs.stdenv.cc.libc}/lib/*.so*,
+ mr ${getLib pkgs.attr}/lib/libattr*.so*,
+ mr ${getLib pkgs.c-ares}/lib/libcares*.so*,
+ mr ${getLib pkgs.curl}/lib/libcurl*.so*,
+ mr ${getLib pkgs.keyutils}/lib/libkeyutils*.so*,
+ mr ${getLib pkgs.libcap}/lib/libcap*.so*,
+ mr ${getLib pkgs.libevent}/lib/libevent*.so*,
+ mr ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so*,
+ mr ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so*,
+ mr ${getLib pkgs.libkrb5}/lib/lib*.so*,
+ mr ${getLib pkgs.libssh2}/lib/libssh2*.so*,
+ mr ${getLib pkgs.lz4}/lib/liblz4*.so*,
+ mr ${getLib pkgs.nghttp2}/lib/libnghttp2*.so*,
+ mr ${getLib pkgs.openssl}/lib/libcrypto*.so*,
+ mr ${getLib pkgs.openssl}/lib/libssl*.so*,
+ mr ${getLib pkgs.systemd}/lib/libsystemd*.so*,
+ mr ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so*,
+ mr ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so*,
+ mr ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so*,
+ mr ${getLib pkgs.xz}/lib/liblzma*.so*,
+ mr ${getLib pkgs.zlib}/lib/libz*.so*,
- @{PROC}/sys/kernel/random/uuid r,
- @{PROC}/sys/vm/overcommit_memory r,
+ r @{PROC}/sys/kernel/random/uuid,
+ r @{PROC}/sys/vm/overcommit_memory,
+ # @{pid} is not a kernel variable yet but a regexp
+ #r @{PROC}/@{pid}/environ,
+ r @{PROC}/@{pid}/mounts,
+ rwk /tmp/tr_session_id_*,
- ${pkgs.openssl.out}/etc/** r,
- ${pkgs.transmission}/share/transmission/** r,
+ r ${pkgs.openssl.out}/etc/**,
+ r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
+ r ${pkgs.transmission}/share/transmission/**,
- owner ${settingsDir}/** rw,
-
- ${fullSettings.download-dir}/** rw,
- ${optionalString fullSettings.incomplete-dir-enabled ''
- ${fullSettings.incomplete-dir}/** rw,
+ owner rw ${cfg.home}/${settingsDir}/**,
+ rw ${cfg.settings.download-dir}/**,
+ ${optionalString cfg.settings.incomplete-dir-enabled ''
+ rw ${cfg.settings.incomplete-dir}/**,
''}
+ profile dirs {
+ rw ${cfg.settings.download-dir}/**,
+ ${optionalString cfg.settings.incomplete-dir-enabled ''
+ rw ${cfg.settings.incomplete-dir}/**,
+ ''}
+ }
+
+ ${optionalString (cfg.settings.script-torrent-done-enabled &&
+ cfg.settings.script-torrent-done-filename != "") ''
+ # Stack transmission_directories profile on top of
+ # any existing profile for script-torrent-done-filename
+ # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
+ # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
+ px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
+ ''}
+
+ # FIXME: enable customizing using https://github.com/NixOS/nixpkgs/pull/93457
+ # include
}
- '')
- ];
+ '';
};
+ meta.maintainers = with lib.maintainers; [ julm ];
}
diff --git a/nixos/modules/virtualisation/lxc.nix b/nixos/modules/virtualisation/lxc.nix
index f484d5ee59a..a2f4a9867c6 100644
--- a/nixos/modules/virtualisation/lxc.nix
+++ b/nixos/modules/virtualisation/lxc.nix
@@ -74,9 +74,13 @@ in
systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
security.apparmor.packages = [ pkgs.lxc ];
- security.apparmor.profiles = [
- "${pkgs.lxc}/etc/apparmor.d/lxc-containers"
- "${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start"
- ];
+ security.apparmor.policies = {
+ "bin/lxc-start".profile = ''
+ #include ${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start
+ '';
+ "lxc-containers".profile = ''
+ #include ${pkgs.lxc}/etc/apparmor.d/lxc-containers
+ '';
+ };
};
}
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index 3958fc2c1d7..84c8e88f8b4 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -93,11 +93,15 @@ in
security.apparmor = {
enable = true;
- profiles = [
- "${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start"
- "${cfg.lxcPackage}/etc/apparmor.d/lxc-containers"
- ];
packages = [ cfg.lxcPackage ];
+ policies = {
+ "bin/lxc-start".profile = ''
+ #include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
+ '';
+ "lxc-containers".profile = ''
+ #include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
+ '';
+ };
};
systemd.services.lxd = {
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index 0a97d5556a2..c195b60cd56 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -19,6 +19,7 @@ let
externalClient2Address = "80.100.100.2";
externalTrackerAddress = "80.100.100.3";
+ download-dir = "/var/lib/transmission/Downloads";
transmissionConfig = { ... }: {
environment.systemPackages = [ pkgs.transmission ];
services.transmission = {
@@ -26,6 +27,7 @@ let
settings = {
dht-enabled = false;
message-level = 3;
+ inherit download-dir;
};
};
};
@@ -117,12 +119,12 @@ in
router.wait_for_unit("miniupnpd")
# Create the torrent.
- tracker.succeed("mkdir /tmp/data")
+ tracker.succeed("mkdir ${download-dir}/data")
tracker.succeed(
- "cp ${file} /tmp/data/test.tar.bz2"
+ "cp ${file} ${download-dir}/data/test.tar.bz2"
)
tracker.succeed(
- "transmission-create /tmp/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
+ "transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
)
tracker.succeed("chmod 644 /tmp/test.torrent")
@@ -133,18 +135,16 @@ in
# Start the initial seeder.
tracker.succeed(
- "transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data"
+ "transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
)
# Now we should be able to download from the client behind the NAT.
tracker.wait_for_unit("httpd")
client1.wait_for_unit("network-online.target")
+ client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
+ client1.wait_for_file("${download-dir}/test.tar.bz2")
client1.succeed(
- "transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &"
- )
- client1.wait_for_file("/tmp/test.tar.bz2")
- client1.succeed(
- "cmp /tmp/test.tar.bz2 ${file}"
+ "cmp ${download-dir}/test.tar.bz2 ${file}"
)
# Bring down the initial seeder.
@@ -154,11 +154,11 @@ in
# the first client created a NAT hole in the router.
client2.wait_for_unit("network-online.target")
client2.succeed(
- "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &"
+ "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
)
- client2.wait_for_file("/tmp/test.tar.bz2")
+ client2.wait_for_file("${download-dir}/test.tar.bz2")
client2.succeed(
- "cmp /tmp/test.tar.bz2 ${file}"
+ "cmp ${download-dir}/test.tar.bz2 ${file}"
)
'';
})
diff --git a/pkgs/os-specific/linux/apparmor/default.nix b/pkgs/os-specific/linux/apparmor/default.nix
index 66c2582603c..0205abc290b 100644
--- a/pkgs/os-specific/linux/apparmor/default.nix
+++ b/pkgs/os-specific/linux/apparmor/default.nix
@@ -10,6 +10,10 @@
, pam
, libnotify
, buildPackages
+, coreutils
+, gnugrep
+, gnused
+, writeShellScript
}:
let
@@ -38,6 +42,22 @@ let
sha256 = "0xw028iqp69j9mxv0kbwraplgkj5i5djdlgf0anpkc5cdbsf96r9";
};
+ aa-teardown = writeShellScript "aa-teardown" ''
+ SECURITYFS=/sys/kernel/security
+ SFS_MOUNTPOINT="$SECURITYFS/apparmor"
+ ${gnused}/bin/sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | \
+ LC_COLLATE=C ${coreutils}/bin/sort | ${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}"
+ }
+ '';
+
prePatchCommon = ''
substituteInPlace ./common/Make.rules --replace "/usr/bin/pod2man" "${buildPackages.perl}/bin/pod2man"
substituteInPlace ./common/Make.rules --replace "/usr/bin/pod2html" "${buildPackages.perl}/bin/pod2html"
@@ -142,6 +162,8 @@ let
# aa-notify checks its name and does not work named ".aa-notify-wrapped"
mv $out/bin/aa-notify $out/bin/aa-notify-wrapped
makeWrapper ${perl}/bin/perl $out/bin/aa-notify --set PERL5LIB ${libapparmor}/${perl.libPrefix} --add-flags $out/bin/aa-notify-wrapped
+
+ ln -s ${aa-teardown} $out/bin/aa-teardown
'';
inherit doCheck;