From 86c0167b27742ab4f50676f308a243b9d6af0bcd Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Tue, 21 Jul 2020 05:48:02 +0200 Subject: [PATCH 01/16] nix: use nixpkgs/patches/wip.diff instead of nixpkgs/overlays.nix and nixos/modules.nix --- nixos/modules.nix | 5 +- nixos/modules/config/fonts/fontconfig.nix | 534 +++++++ nixos/modules/install/ssh-nixos.nix | 60 +- nixos/modules/security/apparmor-suid.nix | 58 + nixos/modules/security/apparmor.nix | 188 +++ nixos/modules/security/apparmor/profiles.nix | 335 +++++ nixos/modules/security/pass.nix | 10 +- nixos/modules/services/mail/mlmmj.nix | 227 +++ nixos/modules/virtualisation/lxc.nix | 86 ++ nixos/modules/virtualisation/lxd.nix | 154 +++ nixpkgs/patches/wip.diff | 1305 ++++++++++++++++++ 11 files changed, 2952 insertions(+), 10 deletions(-) create mode 100644 nixos/modules/config/fonts/fontconfig.nix create mode 100644 nixos/modules/security/apparmor-suid.nix create mode 100644 nixos/modules/security/apparmor.nix create mode 100644 nixos/modules/security/apparmor/profiles.nix create mode 100644 nixos/modules/services/mail/mlmmj.nix create mode 100644 nixos/modules/virtualisation/lxc.nix create mode 100644 nixos/modules/virtualisation/lxd.nix create mode 100644 nixpkgs/patches/wip.diff diff --git a/nixos/modules.nix b/nixos/modules.nix index 855f8c9..0510481 100644 --- a/nixos/modules.nix +++ b/nixos/modules.nix @@ -6,15 +6,12 @@ imports = [ modules/install/ssh-nixos.nix modules/security/pass.nix modules/services/networking/domains.nix - #modules/services/networking/knot.nix modules/services/databases/openldap.nix modules/services/mail/public-inbox.nix #modules/services/mail/mlmmj.nix - modules/services/torrent/transmission.nix ]; disabledModules = [ - "services/mail/public-inbox.nix" "services/mail/mlmmj.nix" - "services/torrent/transmission.nix" + "services/mail/public-inbox.nix" ]; } diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix new file mode 100644 index 0000000..27cbc93 --- /dev/null +++ b/nixos/modules/config/fonts/fontconfig.nix @@ -0,0 +1,534 @@ +/* + +NixOS support 2 fontconfig versions, "support" and "latest". + +- "latest" refers to default fontconfig package (pkgs.fontconfig). + configuration files are linked to /etc/fonts/VERSION/conf.d/ +- "support" refers to supportPkg (pkgs."fontconfig_${supportVersion}"). + configuration files are linked to /etc/fonts/conf.d/ + +This module generates a package containing configuration files and link it in /etc/fonts. + +Fontconfig reads files in folder name / file name order, so the number prepended to the configuration file name decide the order of parsing. +Low number means high priority. + +*/ + +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.fonts.fontconfig; + + fcBool = x: "" + (boolToString x) + ""; + + # back-supported fontconfig version and package + # version is used for font cache generation + supportVersion = "210"; + supportPkg = pkgs."fontconfig_${supportVersion}"; + + # latest fontconfig version and package + # version is used for configuration folder name, /etc/fonts/VERSION/ + # note: format differs from supportVersion and can not be used with makeCacheConf + latestVersion = pkgs.fontconfig.configVersion; + latestPkg = pkgs.fontconfig; + + # supported version fonts.conf + supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; }; + + # configuration file to read fontconfig cache + # version dependent + # priority 0 + cacheConfSupport = makeCacheConf { version = supportVersion; }; + cacheConfLatest = makeCacheConf {}; + + # generate the font cache setting file for a fontconfig version + # use latest when no version is passed + # When cross-compiling, we can’t generate the cache, so we skip the + # part. fontconfig still works but is a little slower in + # looking things up. + makeCacheConf = { version ? null }: + let + fcPackage = if version == null + then "fontconfig" + else "fontconfig_${version}"; + makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; }; + cache = makeCache pkgs.${fcPackage}; + cache32 = makeCache pkgs.pkgsi686Linux.${fcPackage}; + in + pkgs.writeText "fc-00-nixos-cache.conf" '' + + + + + ${concatStringsSep "\n" (map (font: "${font}") config.fonts.fonts)} + ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) '' + + ${cache} + ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) '' + ${cache32} + ''} + ''} + + ''; + + # rendering settings configuration file + # priority 10 + renderConf = pkgs.writeText "fc-10-nixos-rendering.conf" '' + + + + + + + + ${fcBool cfg.hinting.enable} + + + ${fcBool cfg.hinting.autohint} + + + hintslight + + + ${fcBool cfg.antialias} + + + ${cfg.subpixel.rgba} + + + lcd${cfg.subpixel.lcdfilter} + + + + ${optionalString (cfg.dpi != 0) '' + + + ${toString cfg.dpi} + + + ''} + + + ''; + + # local configuration file + localConf = pkgs.writeText "fc-local.conf" cfg.localConf; + + # default fonts configuration file + # priority 52 + defaultFontsConf = + let genDefault = fonts: name: + optionalString (fonts != []) '' + + ${name} + + ${concatStringsSep "" + (map (font: '' + ${font} + '') fonts)} + + + ''; + in + pkgs.writeText "fc-52-nixos-default-fonts.conf" '' + + + + + + ${genDefault cfg.defaultFonts.sansSerif "sans-serif"} + + ${genDefault cfg.defaultFonts.serif "serif"} + + ${genDefault cfg.defaultFonts.monospace "monospace"} + + ${genDefault cfg.defaultFonts.emoji "emoji"} + + + ''; + + # bitmap font options + # priority 53 + rejectBitmaps = pkgs.writeText "fc-53-no-bitmaps.conf" '' + + + + + ${optionalString (!cfg.allowBitmaps) '' + + + + + false + + + + ''} + + + + + ${fcBool cfg.useEmbeddedBitmaps} + + + + + ''; + + # reject Type 1 fonts + # priority 53 + rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" '' + + + + + + + + + Type 1 + + + + + + ''; + + # fontconfig configuration package + confPkg = pkgs.runCommand "fontconfig-conf" { + preferLocalBuild = true; + } '' + support_folder=$out/etc/fonts/conf.d + latest_folder=$out/etc/fonts/${latestVersion}/conf.d + + mkdir -p $support_folder + mkdir -p $latest_folder + + # fonts.conf + ln -s ${supportFontsConf} $support_folder/../fonts.conf + ln -s ${latestPkg.out}/etc/fonts/fonts.conf \ + $latest_folder/../fonts.conf + + # fontconfig default config files + ln -s ${supportPkg.out}/etc/fonts/conf.d/*.conf \ + $support_folder/ + ln -s ${latestPkg.out}/etc/fonts/conf.d/*.conf \ + $latest_folder/ + + # update latest 51-local.conf path to look at the latest local.conf + rm $latest_folder/51-local.conf + + substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \ + $latest_folder/51-local.conf \ + --replace local.conf /etc/fonts/${latestVersion}/local.conf + + # 00-nixos-cache.conf + ln -s ${cacheConfSupport} \ + $support_folder/00-nixos-cache.conf + ln -s ${cacheConfLatest} $latest_folder/00-nixos-cache.conf + + # 10-nixos-rendering.conf + ln -s ${renderConf} $support_folder/10-nixos-rendering.conf + ln -s ${renderConf} $latest_folder/10-nixos-rendering.conf + + # 50-user.conf + ${optionalString (!cfg.includeUserConf) '' + rm $support_folder/50-user.conf + rm $latest_folder/50-user.conf + ''} + + # local.conf (indirect priority 51) + ${optionalString (cfg.localConf != "") '' + ln -s ${localConf} $support_folder/../local.conf + ln -s ${localConf} $latest_folder/../local.conf + ''} + + # 52-nixos-default-fonts.conf + ln -s ${defaultFontsConf} $support_folder/52-nixos-default-fonts.conf + ln -s ${defaultFontsConf} $latest_folder/52-nixos-default-fonts.conf + + # 53-no-bitmaps.conf + ln -s ${rejectBitmaps} $support_folder/53-no-bitmaps.conf + ln -s ${rejectBitmaps} $latest_folder/53-no-bitmaps.conf + + ${optionalString (!cfg.allowType1) '' + # 53-nixos-reject-type1.conf + ln -s ${rejectType1} $support_folder/53-nixos-reject-type1.conf + ln -s ${rejectType1} $latest_folder/53-nixos-reject-type1.conf + ''} + ''; + + # Package with configuration files + # this merge all the packages in the fonts.fontconfig.confPackages list + fontconfigEtc = pkgs.buildEnv { + name = "fontconfig-etc"; + paths = cfg.confPackages; + ignoreCollisions = true; + }; +in +{ + imports = [ + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowBitmaps" ] [ "fonts" "fontconfig" "allowBitmaps" ]) + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowType1" ] [ "fonts" "fontconfig" "allowType1" ]) + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ]) + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ]) + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ]) + (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "") + (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "") + (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "") + ] ++ lib.forEach [ "enable" "substitutions" "preset" ] + (opt: lib.mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] '' + The fonts.fontconfig.ultimate module and configuration is obsolete. + The repository has since been archived and activity has ceased. + https://github.com/bohoomil/fontconfig-ultimate/issues/171. + No action should be needed for font configuration, as the fonts.fontconfig + module is already used by default. + ''); + + options = { + + fonts = { + + fontconfig = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + If enabled, a Fontconfig configuration file will be built + pointing to a set of default fonts. If you don't care about + running X11 applications or any other program that uses + Fontconfig, you can turn this option off and prevent a + dependency on all those fonts. + ''; + }; + + confPackages = mkOption { + internal = true; + type = with types; listOf path; + default = [ ]; + description = '' + Fontconfig configuration packages. + ''; + }; + + antialias = mkOption { + type = types.bool; + default = true; + description = '' + Enable font antialiasing. At high resolution (> 200 DPI), + antialiasing has no visible effect; users of such displays may want + to disable this option. + ''; + }; + + dpi = mkOption { + type = types.int; + default = 0; + description = '' + Force DPI setting. Setting to 0 disables DPI + forcing; the DPI detected for the display will be used. + ''; + }; + + localConf = mkOption { + type = types.lines; + default = ""; + description = '' + System-wide customization file contents, has higher priority than + defaultFonts settings. + ''; + }; + + defaultFonts = { + monospace = mkOption { + type = types.listOf types.str; + default = ["DejaVu Sans Mono"]; + description = '' + System-wide default monospace font(s). Multiple fonts may be + listed in case multiple languages must be supported. + ''; + }; + + sansSerif = mkOption { + type = types.listOf types.str; + default = ["DejaVu Sans"]; + description = '' + System-wide default sans serif font(s). Multiple fonts may be + listed in case multiple languages must be supported. + ''; + }; + + serif = mkOption { + type = types.listOf types.str; + default = ["DejaVu Serif"]; + description = '' + System-wide default serif font(s). Multiple fonts may be listed + in case multiple languages must be supported. + ''; + }; + + emoji = mkOption { + type = types.listOf types.str; + default = ["Noto Color Emoji"]; + description = '' + System-wide default emoji font(s). Multiple fonts may be listed + in case a font does not support all emoji. + + Note that fontconfig matches color emoji fonts preferentially, + so if you want to use a black and white font while having + a color font installed (eg. Noto Color Emoji installed alongside + Noto Emoji), fontconfig will still choose the color font even + when it is later in the list. + ''; + }; + }; + + hinting = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Enable font hinting. Hinting aligns glyphs to pixel boundaries to + improve rendering sharpness at low resolution. At high resolution + (> 200 dpi) hinting will do nothing (at best); users of such + displays may want to disable this option. + ''; + }; + + autohint = mkOption { + type = types.bool; + default = false; + description = '' + Enable the autohinter in place of the default interpreter. + The results are usually lower quality than correctly-hinted + fonts, but better than unhinted fonts. + ''; + }; + }; + + includeUserConf = mkOption { + type = types.bool; + default = true; + description = '' + Include the user configuration from + ~/.config/fontconfig/fonts.conf or + ~/.config/fontconfig/conf.d. + ''; + }; + + subpixel = { + + rgba = mkOption { + default = "rgb"; + type = types.enum ["rgb" "bgr" "vrgb" "vbgr" "none"]; + description = '' + Subpixel order. The overwhelming majority of displays are + rgb in their normal orientation. Select + vrgb for mounting such a display 90 degrees + clockwise from its normal orientation or vbgr + for mounting 90 degrees counter-clockwise. Select + bgr in the unlikely event of mounting 180 + degrees from the normal orientation. Reverse these directions in + the improbable event that the display's native subpixel order is + bgr. + ''; + }; + + lcdfilter = mkOption { + default = "default"; + type = types.enum ["none" "default" "light" "legacy"]; + description = '' + FreeType LCD filter. At high resolution (> 200 DPI), LCD filtering + has no visible effect; users of such displays may want to select + none. + ''; + }; + + }; + + cache32Bit = mkOption { + default = false; + type = types.bool; + description = '' + Generate system fonts cache for 32-bit applications. + ''; + }; + + allowBitmaps = mkOption { + type = types.bool; + default = true; + description = '' + Allow bitmap fonts. Set to false to ban all + bitmap fonts. + ''; + }; + + allowType1 = mkOption { + type = types.bool; + default = false; + description = '' + Allow Type-1 fonts. Default is false because of + poor rendering. + ''; + }; + + useEmbeddedBitmaps = mkOption { + type = types.bool; + default = false; + description = ''Use embedded bitmaps in fonts like Calibri.''; + }; + + }; + + }; + + }; + config = mkMerge [ + (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, + + substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \ + $latest_folder/51-local.conf \ + --replace local.conf /etc/fonts/${latestVersion}/local.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/install/ssh-nixos.nix b/nixos/modules/install/ssh-nixos.nix index a07b297..3232f24 100644 --- a/nixos/modules/install/ssh-nixos.nix +++ b/nixos/modules/install/ssh-nixos.nix @@ -11,12 +11,44 @@ options.install.ssh-nixos = { type = types.listOf types.package; default = []; apply = lib.makeBinPath; + description = "Packages to be added to the PATH of the install script."; }; script = lib.mkOption { type = types.lines; default = ""; + example = '' + lib.mkBefore '''''' + gpg --decrypt initrd/ssh.key.gpg | + ssh root@''${config.install.ssh-nixos.target} \ + install -D -m 400 -o root -g root /dev/stdin /root/initrd/ssh.key + ''''''; + ''; + description = '' + Install script copying the configured NixOS to the target + and switching to the new configuration. + It is made available here for prepending or appending commands + with the usual mkBefore and mkAfter. + In case you run it often or add multiple ssh calls to it, + consider configuring the OpenSSH client with ControlMaster auto + to keep the SSH connexion alive between calls to literal. + + This script is usually run with: + + $ nix run system.config.install.ssh-nixos -f nixos.nix + + where nixos.nix can be: + + import { + system = "x86_64-linux"; + configuration = { config, lib, pkgs }: { + # Your usual configuration.nix content can go here + }; + } + + ''; apply = script: pkgs.writeShellScriptBin nixRunDefaultCommand '' set -eu + set -o pipefail PATH="$PATH:${cfg.PATH}" set -x ${script} @@ -24,7 +56,25 @@ options.install.ssh-nixos = { }; target = lib.mkOption { type = types.str; - default = "root@${networking.hostName}.${networking.domain}"; + default = "${networking.hostName}.${networking.domain}"; + example = "192.168.1.10"; + description = "Destination where to install NixOS by SSH."; + }; + sshFlags = lib.mkOption { + type = types.listOf types.str; + default = ["--substitute-on-destination"]; + description = '' + Extra flags passed to ssh. + Environment variable SSH_FLAGS can also be used at runtime. + ''; + }; + nixCopyFlags = lib.mkOption { + type = types.listOf types.str; + default = ["--substitute-on-destination"]; + description = '' + Extra flags passed to nix copy. + Environment variable SSH_FLAGS can also be used at runtime. + ''; }; profile = lib.mkOption { type = types.str; @@ -35,11 +85,11 @@ config = { install.ssh-nixos.PATH = with pkgs; [nix openssh]; install.ssh-nixos.script = let nixos = config.system.build.toplevel; in '' - nix ''${TRACE:+-L} copy \ - --to ssh://${cfg.target} --substitute-on-destination \ + nix ''${NIX_FLAGS:-} copy \ + --to ssh://root@${cfg.target} ${lib.concatStringsSep " " cfg.nixCopyFlags} ''${NIX_COPY_FLAGS:-} \ ${nixos} - ssh ${cfg.target} nix-env --profile "${cfg.profile}" --set "${nixos}" \ - '&&' "${cfg.profile}"/bin/switch-to-configuration "''${switch:-switch}" + ssh ''${SSH_FLAGS:-} 'root@${cfg.target}' nix-env --profile '${cfg.profile}' --set '${nixos}' \ + '&&' '${cfg.profile}'/bin/switch-to-configuration "''${NIXOS_SWITCH:-switch}" ''; }; meta.maintainers = [ lib.maintainers.julm ]; diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix new file mode 100644 index 0000000..e6d9467 --- /dev/null +++ b/nixos/modules/security/apparmor-suid.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.security.apparmor; +in +with lib; +{ + imports = [ + (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ]) + ]; + + options.security.apparmor.confineSUIDApplications = mkOption { + type = types.bool; + default = true; + description = '' + Install AppArmor profiles for commonly-used SUID application + to mitigate potential privilege escalation attacks due to bugs + in such applications. + + Currently available profiles: ping + ''; + }; + + config = mkIf (cfg.confineSUIDApplications) { + security.apparmor.policies."bin/ping".profile = '' + #include + /run/wrappers/wrappers.*/ping { + #include + #include + #include + + capability net_raw, + capability setuid, + network inet raw, + + ${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, + + ## 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 new file mode 100644 index 0000000..cf9db76 --- /dev/null +++ b/nixos/modules/security/apparmor.nix @@ -0,0 +1,188 @@ +{ config, lib, pkgs, ... }: + +let + inherit (builtins) head match readFile; + inherit (lib) types; + inherit (config.environment) etc; + cfg = config.security.apparmor; + mkDisableOption = descr: lib.mkEnableOption descr // { default=true; example=false; }; +in + +{ + imports = [ + (lib.mkRemovedOptionModule [ "security" "apparmor" "profiles" ] "Please use the new security.apparmor.policies.") + apparmor/profiles.nix + ]; + options = { + security.apparmor = { + enable = lib.mkEnableOption "Whether to enable the AppArmor Mandatory Access Control system."; + policies = lib.mkOption { + description = '' + AppArmor policies. + ''; + type = types.attrsOf (types.submodule ({ name, config, ... }: { + options = { + enable = mkDisableOption "Whether to load the profile into the kernel."; + enforce = mkDisableOption "Whether to enforce 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 '' + Whether to enable 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 '' + Whether to kill processes which have an AppArmor profile enabled + (in policies) + but are not confined (because AppArmor can only confine new processes). + ''; + }; + }; + + 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 + + parser = ${pkgs.apparmor-parser}/bin/apparmor_parser + ldd = ${pkgs.glibc.bin}/bin/ldd + logger = ${pkgs.utillinux}/bin/logger + + # customize how file ownership permissions are presented + # 0 - off + # 1 - default of what ever mode the log reported + # 2 - force the new permissions to be user + # 3 - force all perms on the rule to be user + default_owner_prompt = 1 + + # custom directory locations to look for #includes + # + # each name should be a valid directory containing possible #include + # candidate files under the profile dir which by default is /etc/apparmor.d. + # + # So an entry of my-includes will allow /etc/apparmor.d/my-includes to + # be used by the yast UI and profiling tools as a source of #include + # files. + custom_includes = + + [qualifiers] + ${pkgs.runtimeShell} = icnu + ${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); + unloadDisabledProfiles = 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 ++ + [ unloadDisabledProfiles ] ++ + 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 0000000..47e7255 --- /dev/null +++ b/nixos/modules/security/apparmor/profiles.nix @@ -0,0 +1,335 @@ +{ 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} ,".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 + /var/lib/acme/*/cert.pem r, + /var/lib/acme/*/chain.pem r, + /var/lib/acme/*/fullchain.pem r, + ''; + "abstractions/ssl_keys" = '' + # security.acme NixOS module + /var/lib/acme/*/full.pem r, + /var/lib/acme/*/key.pem r, + ''; + "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/pass.nix b/nixos/modules/security/pass.nix index c3ec6b6..a2141d5 100644 --- a/nixos/modules/security/pass.nix +++ b/nixos/modules/security/pass.nix @@ -14,6 +14,9 @@ options.security.pass = { description = '' Default path to the password-store of the orchestrating system. ''; + # Does not copy the entire password-store into the Nix store, + # only the keys actually used will be. + apply = toString; }; secrets = lib.mkOption { default = {}; @@ -22,7 +25,7 @@ options.security.pass = { gpg = lib.mkOption { type = types.path; default = builtins.path { - path = toString pass.store + "/${name}.gpg"; + path = pass.store + "/${name}.gpg"; name = "${escapeUnitName name}.gpg"; }; description = '' @@ -139,6 +142,11 @@ config = lib.mkIf (pass.secrets != {}) { set -eux ${secret.postStart} ''; + restartIfChanged = true; + restartTriggers = [ + secret.passphraseFile + secret.gpg + ]; serviceConfig = { Type = "oneshot"; Environment = "GNUPGHOME=${secret.gnupgHome}"; diff --git a/nixos/modules/services/mail/mlmmj.nix b/nixos/modules/services/mail/mlmmj.nix new file mode 100644 index 0000000..efd4f91 --- /dev/null +++ b/nixos/modules/services/mail/mlmmj.nix @@ -0,0 +1,227 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + concatMapLines = f: l: lib.concatStringsSep "\n" (map f l); + + cfg = config.services.mlmmj; + stateDir = "/var/lib/mlmmj"; + spoolDir = "/var/spool/mlmmj"; + listDir = domain: list: "${spoolDir}/${domain}/${list}"; + listCtl = domain: list: "${listDir domain list}/control"; + transport = domain: list: "${domain}--${list}@local.list.mlmmj mlmmj:${domain}/${list}"; + virtual = domain: list: "${list}@${domain} ${domain}--${list}@local.list.mlmmj"; + alias = domain: list: "${list}: \"|${pkgs.mlmmj}/bin/mlmmj-receive -L ${listDir domain list}/\""; + subjectPrefix = list: "[${list}]"; + listAddress = domain: list: "${list}@${domain}"; + customHeaders = domain: list: [ "List-Id: ${list}" "Reply-To: ${list}@${domain}" ]; + footer = domain: list: "-- \nTo unsubscribe send a mail to ${list}+unsubscribe@${domain}"; + createList = d: l: + let ctlDir = listCtl d l; in + '' + for DIR in incoming queue queue/discarded archive text subconf unsubconf \ + bounce control moderation subscribers.d digesters.d requeue \ + nomailsubs.d + do + mkdir -p '${listDir d l}'/"$DIR" + done + ${pkgs.coreutils}/bin/mkdir -p ${ctlDir} + echo ${listAddress d l} > '${ctlDir}/listaddress' + [ ! -e ${ctlDir}/customheaders ] && \ + echo "${lib.concatStringsSep "\n" (customHeaders d l)}" > '${ctlDir}/customheaders' + [ ! -e ${ctlDir}/footer ] && \ + echo ${footer d l} > '${ctlDir}/footer' + [ ! -e ${ctlDir}/prefix ] && \ + echo ${subjectPrefix l} > '${ctlDir}/prefix' + ''; + customHeaderOptions = { + name = lib.mkOption { + type = types.str; + }; + value = lib.mkOption { + type = types.str; + }; + } + + mailingListConfig = config: { + settings = { + use_template = lib.mkDefault config.useTemplate; + recursive = lib.mkDefault config.recursive; + process_children_only = lib.mkDefault config.processChildrenOnly; + }; + }; + +in + +{ + + ###### interface + + options = { + + services.mlmmj = { + + enable = mkOption { + type = types.bool; + default = false; + description = "Enable mlmmj"; + }; + + user = mkOption { + type = types.str; + default = "mlmmj"; + description = "mailinglist local user"; + }; + + group = mkOption { + type = types.str; + default = "mlmmj"; + description = "mailinglist local group"; + }; + + listDomain = mkOption { + type = types.str; + default = "localhost"; + description = "Set the mailing list domain"; + }; + + mailLists = mkOption { + type = types.listOf types.str; + default = []; + description = "The collection of hosted maillists"; + }; + + maillists = lib.mkOption { + type = types.attrsOf (types.submodule (domain: { + options = { + type = types.attrsOf (types.submodule (list: { + options = { + customHeaders = lib.mkOption { + type = types.listOf customHeaderOptions; + default = [ + "List-Id: ${list.name}" + "Reply-To: ${list.name}@${domain.name}" + ]; + description = "Custom headers."; + }; + footer = lib.mkOption { + type = types.lines; + default = "-- \nTo unsubscribe send a mail to ${list.name}+unsubscribe@${domain.name}"; + description = "Text to be added as a footer to the mails' body."; + }; + lang = lib.mkOption { + type = types.enum ["ast" "cs" "de" "en" "fi" "fr" "gr" "it" "pt" "sk" "zh-cn"]; + default = "en"; + description = "Language of the templates."; + }; + subjectPrefix = lib.mkOption { + type = types.str; + default = "[${list.name}]"; + description = "String added as a prefix to the Subject header."; + }; + }; + config = mailingListConfig config; + })) + } + })); + default = {}; + description = "Mailing-lists."; + }; + + maintInterval = mkOption { + type = types.str; + default = "20min"; + description = '' + Time interval between mlmmj-maintd runs, see + systemd.time + 7 for format information. + ''; + }; + + postfix = { + enable = mkOption { + type = types.bool; + default = false; + description = "Configure postfix to use mlmmj as a transport"; + }; + }; + + }; + + }; + + ###### implementation + + config = mkIf cfg.enable { + + users.users.${cfg.user} = { + description = "mlmmj user"; + home = stateDir; + createHome = true; + uid = config.ids.uids.mlmmj; + group = cfg.group; + useDefaultShell = true; + }; + + users.groups.${cfg.group} = { + gid = config.ids.gids.mlmmj; + }; + + services.postfix = mkIf cfg.postfix.enable { + enable = true; + recipientDelimiter= "+"; + masterConfig.mlmmj = { + type = "unix"; + private = true; + privileged = true; + chroot = false; + wakeup = 0; + command = "pipe"; + args = [ + "flags=ORhu" + "user=${cfg.user}:${cfg.group}" + "argv=${pkgs.mlmmj}/bin/mlmmj-receive" + "-F" + "-L" + "${spoolDir}/$nexthop" + ]; + }; + + extraAliases = lib.concatMapStringsSep "\n" (alias cfg.listDomain) cfg.mailLists; + + config = { + propagate_unmatched_extensions = ["virtual"]; + mlmmj_destination_recipient_limit = "1"; + }; + + virtual = lib.concatMapStringsSep "\n" (virtual cfg.listDomain) cfg.mailLists; + transport = lib.concatMapStringsSep "\n" (transport cfg.listDomain) cfg.mailLists; + }; + + environment.systemPackages = [ pkgs.mlmmj ]; + + system.activationScripts.mlmmj = '' + ${pkgs.coreutils}/bin/mkdir -p ${stateDir} ${spoolDir}/${cfg.listDomain} + ${lib.concatMapStringsSep "\n" (createList cfg.listDomain) cfg.mailLists} + ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${spoolDir} + ''; + + systemd.services.mlmmj-maintd = { + description = "mlmmj maintenance daemon"; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = "${pkgs.mlmmj}/bin/mlmmj-maintd -F -d ${spoolDir}/${cfg.listDomain}"; + }; + }; + + systemd.timers.mlmmj-maintd = { + description = "mlmmj maintenance timer"; + timerConfig.OnUnitActiveSec = cfg.maintInterval; + wantedBy = [ "timers.target" ]; + }; + }; + +} diff --git a/nixos/modules/virtualisation/lxc.nix b/nixos/modules/virtualisation/lxc.nix new file mode 100644 index 0000000..a2f4a98 --- /dev/null +++ b/nixos/modules/virtualisation/lxc.nix @@ -0,0 +1,86 @@ +# LXC Configuration + +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.virtualisation.lxc; + +in + +{ + ###### interface + + options.virtualisation.lxc = { + enable = + mkOption { + type = types.bool; + default = false; + description = + '' + This enables Linux Containers (LXC), which provides tools + for creating and managing system or application containers + on Linux. + ''; + }; + + systemConfig = + mkOption { + type = types.lines; + default = ""; + description = + '' + This is the system-wide LXC config. See + lxc.system.conf + 5. + ''; + }; + + defaultConfig = + mkOption { + type = types.lines; + default = ""; + description = + '' + Default config (default.conf) for new containers, i.e. for + network config. See lxc.container.conf + 5. + ''; + }; + + usernetConfig = + mkOption { + type = types.lines; + default = ""; + description = + '' + This is the config file for managing unprivileged user network + administration access in LXC. See + lxc-usernet5 + . + ''; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.lxc ]; + environment.etc."lxc/lxc.conf".text = cfg.systemConfig; + environment.etc."lxc/lxc-usernet".text = cfg.usernetConfig; + environment.etc."lxc/default.conf".text = cfg.defaultConfig; + systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ]; + + security.apparmor.packages = [ pkgs.lxc ]; + 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 new file mode 100644 index 0000000..84c8e88 --- /dev/null +++ b/nixos/modules/virtualisation/lxd.nix @@ -0,0 +1,154 @@ +# Systemd services for lxd. + +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.virtualisation.lxd; + zfsCfg = config.boot.zfs; + +in + +{ + ###### interface + + options = { + virtualisation.lxd = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + This option enables lxd, a daemon that manages + containers. Users in the "lxd" group can interact with + the daemon (e.g. to start or stop containers) using the + lxc command line tool, among others. + + Most of the time, you'll also want to start lxcfs, so + that containers can "see" the limits: + + virtualisation.lxc.lxcfs.enable = true; + + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.lxd.override { nftablesSupport = config.networking.nftables.enable; }; + defaultText = "pkgs.lxd"; + description = '' + The LXD package to use. + ''; + }; + + lxcPackage = mkOption { + type = types.package; + default = pkgs.lxc; + defaultText = "pkgs.lxc"; + description = '' + The LXC package to use with LXD (required for AppArmor profiles). + ''; + }; + + zfsPackage = mkOption { + type = types.package; + default = with pkgs; if zfsCfg.enableUnstable then zfsUnstable else zfs; + defaultText = "pkgs.zfs"; + description = '' + The ZFS package to use with LXD. + ''; + }; + + zfsSupport = mkOption { + type = types.bool; + default = false; + description = '' + Enables lxd to use zfs as a storage for containers. + + This option is enabled by default if a zfs pool is configured + with nixos. + ''; + }; + + recommendedSysctlSettings = mkOption { + type = types.bool; + default = false; + description = '' + enables various settings to avoid common pitfalls when + running containers requiring many file operations. + Fixes errors like "Too many open files" or + "neighbour: ndisc_cache: neighbor table overflow!". + See https://lxd.readthedocs.io/en/latest/production-setup/ + for details. + ''; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + security.apparmor = { + enable = true; + 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 = { + description = "LXD Container Management Daemon"; + + wantedBy = [ "multi-user.target" ]; + after = [ "systemd-udev-settle.service" ]; + + path = lib.optional cfg.zfsSupport cfg.zfsPackage; + + preStart = '' + mkdir -m 0755 -p /var/lib/lxc/rootfs + ''; + + serviceConfig = { + ExecStart = "@${cfg.package}/bin/lxd lxd --group lxd"; + Type = "simple"; + KillMode = "process"; # when stopping, leave the containers alone + LimitMEMLOCK = "infinity"; + LimitNOFILE = "1048576"; + LimitNPROC = "infinity"; + TasksMax = "infinity"; + + # By default, `lxd` loads configuration files from hard-coded + # `/usr/share/lxc/config` - since this is a no-go for us, we have to + # explicitly tell it where the actual configuration files are + Environment = mkIf (config.virtualisation.lxc.lxcfs.enable) + "LXD_LXC_TEMPLATE_CONFIG=${pkgs.lxcfs}/share/lxc/config"; + }; + }; + + users.groups.lxd.gid = config.ids.gids.lxd; + + users.users.root = { + subUidRanges = [ { startUid = 1000000; count = 65536; } ]; + subGidRanges = [ { startGid = 1000000; count = 65536; } ]; + }; + + boot.kernel.sysctl = mkIf cfg.recommendedSysctlSettings { + "fs.inotify.max_queued_events" = 1048576; + "fs.inotify.max_user_instances" = 1048576; + "fs.inotify.max_user_watches" = 1048576; + "vm.max_map_count" = 262144; + "kernel.dmesg_restrict" = 1; + "net.ipv4.neigh.default.gc_thresh3" = 8192; + "net.ipv6.neigh.default.gc_thresh3" = 8192; + "kernel.keys.maxkeys" = 2000; + }; + }; +} diff --git a/nixpkgs/patches/wip.diff b/nixpkgs/patches/wip.diff new file mode 100644 index 0000000..9831edd --- /dev/null +++ b/nixpkgs/patches/wip.diff @@ -0,0 +1,1305 @@ +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..a1c6d949bfe 100644 +--- a/nixos/modules/services/torrent/transmission.nix ++++ b/nixos/modules/services/torrent/transmission.nix +@@ -1,52 +1,56 @@ +-{ 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 +- ''; ++ stateDir = "/var/lib/transmission"; ++ # TODO: switch to configGen.json once RFC0042 is implemented ++ settingsFile = pkgs.writeText "settings.json" (builtins.toJSON (cfg.settings // { ++ download-dir = "${stateDir}/Downloads"; ++ incomplete-dir = "${stateDir}/.incomplete"; ++ })); ++ settingsDir = ".config/transmission-daemon"; ++ makeAbsolute = base: path: ++ if builtins.match "^/.*" path == null ++ then base+"/"+path else path; + 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://${cfg.settings.rpc-bind-address}:${toString cfg.settings.rpc-port}/ 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 ${cfg.settings.download-dir} 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 = attrs: ++ let super = recursiveUpdate default attrs; in ++ super // { ++ download-dir = makeAbsolute cfg.home super.download-dir; ++ incomplete-dir = makeAbsolute cfg.home super.incomplete-dir; ++ }; + default = + { +- download-dir = downloadDir; +- incomplete-dir = incompleteDir; ++ download-dir = "${cfg.home}/Downloads"; ++ incomplete-dir = "${cfg.home}/.incomplete"; + 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; ++ umask = 18; # 0o022 in decimal as expected by Transmission, obtained with: echo $((8#022)) ++ utp-enabled = true; + }; + example = + { +@@ -56,8 +60,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 +75,30 @@ 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 the systemd-tmpfiles-setup service ++ on settings.download-dir ++ and settings.incomplete-dir. + ''; + }; + + 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 = stateDir; + description = '' +- The directory where transmission will create files. ++ The directory where Transmission will create .config/transmission-daemon/. ++ as well as Downloads/ unless settings.download-dir is changed, ++ and .incomplete/ unless settings.incomplete-dir is changed. + ''; + }; + +@@ -100,32 +113,136 @@ 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"; + }; + }; + + config = mkIf cfg.enable { +- 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}' - -" ++ systemd.tmpfiles.rules = ++ optional (cfg.home != stateDir) "d '${cfg.home}/${settingsDir}' 700 '${cfg.user}' '${cfg.group}' - -" ++ ++ [ "d '${cfg.settings.download-dir}' '${cfg.downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -" ] ++ ++ optional cfg.settings.incomplete-dir-enabled ++ "d '${cfg.settings.incomplete-dir}' '${cfg.downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"; ++ ++ assertions = [ ++ { assertion = builtins.match "^/.*" cfg.home != null; ++ message = "`services.transmission.home' 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.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 = { ++ WorkingDirectory = stateDir; ++ # Use "+" because credentialsFile may not be accessible to the User. ++ ExecStartPre = "+" + pkgs.writeShellScript "transmission-prestart" '' ++ set -eux ++ ${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' | ++ install -m 700 -o ${cfg.user} -g ${cfg.group} /dev/stdin '${stateDir}/${settingsDir}/settings.json' ++ ''; ++ ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f"; ++ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; ++ User = cfg.user; ++ Group = cfg.group; ++ StateDirectory = removePrefix "/var/lib/" stateDir + "/" + settingsDir; ++ StateDirectoryMode = "0700"; ++ BindPaths = ++ optional (cfg.home != stateDir) "${cfg.home}/${settingsDir}:${stateDir}/${settingsDir}" ++ ++ [ "${cfg.settings.download-dir}:${stateDir}/Downloads" ] ++ ++ optional cfg.settings.incomplete-dir-enabled "${cfg.settings.incomplete-dir}:${stateDir}/.incomplete"; ++ # The following options give: ++ # systemd-analyze security transmission ++ # → Overall exposure level for transmission.service: 1.5 OK ++ AmbientCapabilities = ""; ++ CapabilityBoundingSet = ""; ++ LockPersonality = true; ++ MemoryDenyWriteExecute = true; ++ NoNewPrivileges = true; ++ PrivateDevices = true; ++ PrivateMounts = true; ++ PrivateNetwork = false; ++ PrivateTmp = true; ++ PrivateUsers = false; ++ ProtectClock = true; ++ ProtectControlGroups = true; ++ ProtectHome = mkDefault true; ++ ProtectHostname = true; ++ ProtectKernelLogs = true; ++ ProtectKernelModules = true; ++ ProtectKernelTunables = true; ++ ProtectSystem = mkDefault "strict"; ++ ReadWritePaths = [ stateDir ]; ++ RemoveIPC = true; ++ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; ++ RestrictNamespaces = true; ++ RestrictRealtime = true; ++ RestrictSUIDSGID = true; ++ # In case transmission crashes with status=31/SYS, ++ # having systemd.coredump.enable = true ++ # and environment.enableDebugInfo = true ++ # enables to use coredumpctl debug to find the denied syscall. ++ SystemCallFilter = [ ++ "@default" ++ "@aio" ++ "@basic-io" ++ #"@chown" ++ #"@clock" ++ #"@cpu-emulation" ++ #"@debug" ++ "@file-system" ++ "@io-event" ++ #"@ipc" ++ #"@keyring" ++ #"@memlock" ++ #"@module" ++ #"@mount" ++ "@network-io" ++ #"@obsolete" ++ #"@pkey" ++ #"@privileged" ++ # Reached when querying infos through RPC (eg. with stig) ++ "quotactl" ++ "@process" ++ #"@raw-io" ++ #"@reboot" ++ #"@resources" ++ #"@setuid" ++ "@signal" ++ #"@swap" ++ "@sync" ++ "@system-service" ++ "@timer" ++ ]; ++ SystemCallArchitectures = "native"; ++ UMask = "0077"; ++ }; + }; + + # It's useful to have transmission in path, e.g. for remote control +@@ -133,32 +250,57 @@ 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 = stateDir; + }; + }); + + 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" '' ++ 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 ]; ++ } ++ ); ++ ++ # 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. ++ boot.kernel.sysctl = mkIf cfg.settings.utp-enabled { ++ "net.core.rmem_max" = mkDefault "4194304"; # 4MB ++ "net.core.wmem_max" = mkDefault "1048576"; # 1MB ++ }; ++ ++ security.apparmor.policies."bin/transmission-daemon".profile = '' + #include + + ${pkgs.transmission}/bin/transmission-daemon { + #include + #include + +- ${getLib pkgs.glibc}/lib/*.so mr, ++ ${getLib pkgs.stdenv.cc.cc}/lib/*.so* mr, ++ ${getLib pkgs.stdenv.cc.libc}/lib/*.so* mr, + ${getLib pkgs.libevent}/lib/libevent*.so* mr, + ${getLib pkgs.curl}/lib/libcurl*.so* mr, + ${getLib pkgs.openssl}/lib/libssl*.so* mr, +@@ -176,27 +318,29 @@ in + ${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, ++ ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so* mr, ++ ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so* mr, ++ ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so* mr, + + @{PROC}/sys/kernel/random/uuid r, + @{PROC}/sys/vm/overcommit_memory r, ++ #@{PROC}/@{pid}/environ r, ++ @{PROC}/@{pid}/mounts r, ++ /tmp/tr_session_id_* rwk, + +- ${pkgs.openssl.out}/etc/** r, ++ ${pkgs.openssl.out}/etc/** r, ++ ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE} r, + ${pkgs.transmission}/share/transmission/** r, + +- owner ${settingsDir}/** rw, ++ owner ${stateDir}/${settingsDir}/** rw, + +- ${fullSettings.download-dir}/** rw, +- ${optionalString fullSettings.incomplete-dir-enabled '' +- ${fullSettings.incomplete-dir}/** rw, ++ ${stateDir}/Downloads/** rw, ++ ${optionalString cfg.settings.incomplete-dir-enabled '' ++ ${stateDir}/.incomplete/** rw, + ''} + } +- '') +- ]; ++ ''; + }; + ++ 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; -- 2.49.0 From d242d89669266fcb10d1df4dc69668a86f731563 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Tue, 21 Jul 2020 06:41:22 +0200 Subject: [PATCH 02/16] nix: fix installation comments --- machines/losurdo.nix | 4 ++-- machines/mermet.nix | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/machines/losurdo.nix b/machines/losurdo.nix index 530abef..c932802 100644 --- a/machines/losurdo.nix +++ b/machines/losurdo.nix @@ -3,11 +3,11 @@ # Show configuration options with, for example: # nix-instantiate machines/losurdo.nix --eval -A config.networking.hostName # or: -# nix eval machines.losurdo.config.networking.hostName +# nix eval machines.losurdo.networking.hostName # Install/upgrade with: # nix run config.install.ssh-nixos -f machines/losurdo.nix # or: -# nix run machines.losurdo.config.install.ssh-nixos +# nix run machines.losurdo.install.ssh-nixos { system = "x86_64-linux"; extraArgs = { diff --git a/machines/mermet.nix b/machines/mermet.nix index 2d67d7f..9ce11e5 100644 --- a/machines/mermet.nix +++ b/machines/mermet.nix @@ -3,11 +3,11 @@ # Show configuration options with, for example: # nix-instantiate machines/mermet.nix --eval -A config.networking.hostName # or: -# nix eval machines.mermet.config.networking.hostName +# nix eval machines.mermet.networking.hostName # Install/upgrade with: # nix run config.install.ssh-nixos -f machines/mermet.nix # or: -# nix run machines.mermet.config.install.ssh-nixos +# nix run machines.mermet.install.ssh-nixos { system = "x86_64-linux"; extraArgs = { -- 2.49.0 From 9887c48e648312ee6f0be07bce877cb0e3f87aeb Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Tue, 21 Jul 2020 10:47:55 +0200 Subject: [PATCH 03/16] nix: use nixpkgs/patches/ instead of nixos/modules/ --- .envrc | 2 +- machines/losurdo.nix | 4 +- machines/losurdo/security.nix | 6 +- machines/mermet.nix | 4 +- machines/mermet/security.nix | 6 +- nixos/modules.nix | 2 - nixos/modules/install/ssh-nixos.nix | 96 --------- nixpkgs/patches/installer.ssh-nixos.diff | 130 +++++++++++++ nixpkgs/patches/security.pass.diff | 183 ++++++++++++++++++ .../{wip.diff => transmission+apparmor.diff} | 0 shell.nix | 4 +- 11 files changed, 327 insertions(+), 110 deletions(-) delete mode 100644 nixos/modules/install/ssh-nixos.nix create mode 100644 nixpkgs/patches/installer.ssh-nixos.diff create mode 100644 nixpkgs/patches/security.pass.diff rename nixpkgs/patches/{wip.diff => transmission+apparmor.diff} (100%) diff --git a/.envrc b/.envrc index c147b99..30cb83a 100644 --- a/.envrc +++ b/.envrc @@ -2,7 +2,7 @@ nix_version=2.3.6 nix_openpgp=B541D55301270E0BCF15CA5D8170B4726D7198DE nixpkgs_channel=nixos-unstable-small -nixshell_sources=".envrc shell.nix nodes.nix +nixshell_sources=".envrc shell.nix machines.nix .config/nixpkgs-channel/$nixpkgs_channel.nix $(for d in shell nixpkgs; do test ! -d "$d" || find "$d" -type f -not -name "*~" diff --git a/machines/losurdo.nix b/machines/losurdo.nix index c932802..97df904 100644 --- a/machines/losurdo.nix +++ b/machines/losurdo.nix @@ -5,9 +5,9 @@ # or: # nix eval machines.losurdo.networking.hostName # Install/upgrade with: -# nix run config.install.ssh-nixos -f machines/losurdo.nix +# nix run config.installer.ssh-nixos -f machines/losurdo.nix # or: -# nix run machines.losurdo.install.ssh-nixos +# nix run machines.losurdo.installer.ssh-nixos { system = "x86_64-linux"; extraArgs = { diff --git a/machines/losurdo/security.nix b/machines/losurdo/security.nix index a3d4e5e..5927bd2 100644 --- a/machines/losurdo/security.nix +++ b/machines/losurdo/security.nix @@ -22,15 +22,15 @@ security.pass = { ''; }; }; -install.ssh-nixos = { +installer.ssh-nixos = { PATH = with pkgs; [gnupg openssh]; # Decrypt the rootKey passphrase and the initrd SSH host key # and send them to the target host. script = lib.mkBefore '' gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | - ssh 'root@${config.install.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass + ssh '${config.installer.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass gpg --decrypt '${pass.store}/${initrdKey}.gpg' | - ssh 'root@${config.install.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} + ssh '${config.installer.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} ''; }; boot.initrd.network.ssh.hostKeys = [ "/root/${initrdKey}" ]; diff --git a/machines/mermet.nix b/machines/mermet.nix index 9ce11e5..c8a9820 100644 --- a/machines/mermet.nix +++ b/machines/mermet.nix @@ -5,9 +5,9 @@ # or: # nix eval machines.mermet.networking.hostName # Install/upgrade with: -# nix run config.install.ssh-nixos -f machines/mermet.nix +# nix run config.installer.ssh-nixos -f machines/mermet.nix # or: -# nix run machines.mermet.install.ssh-nixos +# nix run machines.mermet.installer.ssh-nixos { system = "x86_64-linux"; extraArgs = { diff --git a/machines/mermet/security.nix b/machines/mermet/security.nix index 28bd18d..ad9c83a 100644 --- a/machines/mermet/security.nix +++ b/machines/mermet/security.nix @@ -21,15 +21,15 @@ security.pass = { ''; }; }; -install.ssh-nixos = { +installer.ssh-nixos = { PATH = with pkgs; [gnupg openssh]; # Decrypt the rootKey passphrase and the initrd SSH host key # and send them to the target host. script = lib.mkBefore '' gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | - ssh root@'${config.install.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass + ssh '${config.installer.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass gpg --decrypt '${pass.store}/${initrdKey}.gpg' | - ssh root@'${config.install.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} + ssh '${config.installer.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} ''; }; boot.initrd.network.ssh.hostKeys = [ "/root/${initrdKey}" ]; diff --git a/nixos/modules.nix b/nixos/modules.nix index 0510481..eed873b 100644 --- a/nixos/modules.nix +++ b/nixos/modules.nix @@ -3,8 +3,6 @@ # its clearer, safer and more flexible if not quicker. { imports = [ - modules/install/ssh-nixos.nix - modules/security/pass.nix modules/services/networking/domains.nix modules/services/databases/openldap.nix modules/services/mail/public-inbox.nix diff --git a/nixos/modules/install/ssh-nixos.nix b/nixos/modules/install/ssh-nixos.nix deleted file mode 100644 index 3232f24..0000000 --- a/nixos/modules/install/ssh-nixos.nix +++ /dev/null @@ -1,96 +0,0 @@ -{ pkgs, lib, config, ... }: -let - inherit (lib) types; - inherit (config) networking; - cfg = config.install.ssh-nixos; - nixRunDefaultCommand = "bash"; -in -{ -options.install.ssh-nixos = { - PATH = lib.mkOption { - type = types.listOf types.package; - default = []; - apply = lib.makeBinPath; - description = "Packages to be added to the PATH of the install script."; - }; - script = lib.mkOption { - type = types.lines; - default = ""; - example = '' - lib.mkBefore '''''' - gpg --decrypt initrd/ssh.key.gpg | - ssh root@''${config.install.ssh-nixos.target} \ - install -D -m 400 -o root -g root /dev/stdin /root/initrd/ssh.key - ''''''; - ''; - description = '' - Install script copying the configured NixOS to the target - and switching to the new configuration. - It is made available here for prepending or appending commands - with the usual mkBefore and mkAfter. - In case you run it often or add multiple ssh calls to it, - consider configuring the OpenSSH client with ControlMaster auto - to keep the SSH connexion alive between calls to literal. - - This script is usually run with: - - $ nix run system.config.install.ssh-nixos -f nixos.nix - - where nixos.nix can be: - - import { - system = "x86_64-linux"; - configuration = { config, lib, pkgs }: { - # Your usual configuration.nix content can go here - }; - } - - ''; - apply = script: pkgs.writeShellScriptBin nixRunDefaultCommand '' - set -eu - set -o pipefail - PATH="$PATH:${cfg.PATH}" - set -x - ${script} - ''; - }; - target = lib.mkOption { - type = types.str; - default = "${networking.hostName}.${networking.domain}"; - example = "192.168.1.10"; - description = "Destination where to install NixOS by SSH."; - }; - sshFlags = lib.mkOption { - type = types.listOf types.str; - default = ["--substitute-on-destination"]; - description = '' - Extra flags passed to ssh. - Environment variable SSH_FLAGS can also be used at runtime. - ''; - }; - nixCopyFlags = lib.mkOption { - type = types.listOf types.str; - default = ["--substitute-on-destination"]; - description = '' - Extra flags passed to nix copy. - Environment variable SSH_FLAGS can also be used at runtime. - ''; - }; - profile = lib.mkOption { - type = types.str; - default = "/nix/var/nix/profiles/system"; - }; -}; -config = { - install.ssh-nixos.PATH = with pkgs; [nix openssh]; - install.ssh-nixos.script = - let nixos = config.system.build.toplevel; in '' - nix ''${NIX_FLAGS:-} copy \ - --to ssh://root@${cfg.target} ${lib.concatStringsSep " " cfg.nixCopyFlags} ''${NIX_COPY_FLAGS:-} \ - ${nixos} - ssh ''${SSH_FLAGS:-} 'root@${cfg.target}' nix-env --profile '${cfg.profile}' --set '${nixos}' \ - '&&' '${cfg.profile}'/bin/switch-to-configuration "''${NIXOS_SWITCH:-switch}" - ''; -}; -meta.maintainers = [ lib.maintainers.julm ]; -} diff --git a/nixpkgs/patches/installer.ssh-nixos.diff b/nixpkgs/patches/installer.ssh-nixos.diff new file mode 100644 index 0000000..d73503c --- /dev/null +++ b/nixpkgs/patches/installer.ssh-nixos.diff @@ -0,0 +1,130 @@ +diff --git a/nixos/modules/installer/ssh-nixos.nix b/nixos/modules/installer/ssh-nixos.nix +new file mode 100644 +index 00000000000..52ac88799ee +--- /dev/null ++++ b/nixos/modules/installer/ssh-nixos.nix +@@ -0,0 +1,112 @@ ++{ pkgs, lib, config, ... }: ++let ++ inherit (lib) types; ++ inherit (config) networking; ++ cfg = config.installer.ssh-nixos; ++ nixRunDefaultCommand = "bash"; ++ ssh = pkgs.writeShellScriptBin "ssh" '' ++ set -eu ++ PATH=$OLDPATH ++ set -x ++ ssh -l '${cfg.login}' \ ++ ${lib.escapeShellArgs cfg.sshFlags} ''${SSH_FLAGS:-} "$@" ++ ''; ++in ++{ ++options.installer.ssh-nixos = { ++ PATH = lib.mkOption { ++ type = types.listOf types.package; ++ default = []; ++ apply = lib.makeBinPath; ++ description = "Packages to be appended to the PATH of the script."; ++ }; ++ script = lib.mkOption { ++ type = types.lines; ++ default = ""; ++ example = '' ++ lib.mkBefore '''''' ++ gpg --decrypt initrd/ssh.key.gpg | ++ ssh root@''${config.installer.ssh-nixos.target} \ ++ install -D -m 400 -o root -g root /dev/stdin /root/initrd/ssh.key ++ ''''''; ++ ''; ++ description = '' ++ Install script copying the configured NixOS to the target ++ and switching to the new configuration. ++ It is made available here for prepending or appending commands ++ with the usual mkBefore and mkAfter. ++ In case you run it often or add multiple ssh calls to it, ++ consider configuring the OpenSSH client with ControlMaster auto ++ to keep the SSH connexion alive between calls to literal. ++ ++ This script is usually run with: ++ ++ $ nix run system.config.installer.ssh-nixos -f nixos.nix ++ ++ where nixos.nix can be: ++ ++ import { ++ system = "x86_64-linux"; ++ configuration = { config, lib, pkgs }: { ++ # Your usual configuration.nix content can go here ++ }; ++ } ++ ++ ''; ++ apply = script: pkgs.writeShellScriptBin nixRunDefaultCommand '' ++ set -eu ++ set -o pipefail ++ export OLDPATH=$PATH:${cfg.PATH} ++ PATH="${ssh}/bin:$OLDPATH" ++ set -x ++ ${script} ++ ''; ++ }; ++ login = lib.mkOption { ++ type = types.str; ++ default = "root"; ++ example = "admin"; ++ description = "Login name passed to ssh."; ++ }; ++ target = lib.mkOption { ++ type = types.str; ++ default = "${networking.hostName}.${networking.domain}"; ++ example = "192.168.1.10"; ++ description = "Destination where to install NixOS passed to ssh."; ++ }; ++ sshFlags = lib.mkOption { ++ type = types.listOf types.str; ++ default = ["-o" "ControlMaster=auto"]; ++ description = '' ++ Extra flags passed to ssh. ++ Environment variable SSH_FLAGS can also be used at runtime. ++ ''; ++ }; ++ nixCopyFlags = lib.mkOption { ++ type = types.listOf types.str; ++ default = ["--substitute-on-destination"]; ++ description = '' ++ Extra flags passed to nix copy. ++ Environment variable NIX_COPY_FLAGS can also be used at runtime. ++ ''; ++ }; ++ profile = lib.mkOption { ++ type = types.str; ++ default = "/nix/var/nix/profiles/system"; ++ }; ++}; ++config = { ++ installer.ssh-nixos.PATH = with pkgs; [nix openssh]; ++ installer.ssh-nixos.script = ++ let nixos = config.system.build.toplevel; in '' ++ nix ''${NIX_FLAGS:-} copy \ ++ --to ssh://'${cfg.target}' \ ++ ${lib.escapeShellArgs cfg.nixCopyFlags} ''${NIX_COPY_FLAGS:-} \ ++ ${nixos} ++ ssh '${cfg.target}' \ ++ nix-env --profile '${cfg.profile}' --set '${nixos}' '&&' \ ++ '${cfg.profile}'/bin/switch-to-configuration "''${NIXOS_SWITCH:-switch}" ++ ''; ++}; ++meta.maintainers = [ lib.maintainers.julm ]; ++} +diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix +index f361163ca63..15659fde11b 100644 +--- a/nixos/modules/module-list.nix ++++ b/nixos/modules/module-list.nix +@@ -80,6 +80,7 @@ + ./i18n/input-method/ibus.nix + ./i18n/input-method/nabi.nix + ./i18n/input-method/uim.nix ++ ./installer/ssh-nixos.nix + ./installer/tools/tools.nix + ./misc/assertions.nix + ./misc/crashdump.nix diff --git a/nixpkgs/patches/security.pass.diff b/nixpkgs/patches/security.pass.diff new file mode 100644 index 0000000..862b2c1 --- /dev/null +++ b/nixpkgs/patches/security.pass.diff @@ -0,0 +1,183 @@ +diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix +index f361163ca63..5e306de7dad 100644 +--- a/nixos/modules/module-list.nix ++++ b/nixos/modules/module-list.nix +@@ -193,6 +193,7 @@ + ./security/lock-kernel-modules.nix + ./security/misc.nix + ./security/oath.nix ++ ./security/pass.nix + ./security/pam.nix + ./security/pam_usb.nix + ./security/pam_mount.nix +diff --git a/nixos/modules/security/pass.nix b/nixos/modules/security/pass.nix +new file mode 100644 +index 00000000000..a2141d56d16 +--- /dev/null ++++ b/nixos/modules/security/pass.nix +@@ -0,0 +1,165 @@ ++{ pkgs, lib, config, ... }: ++let ++ inherit (builtins) dirOf head listToAttrs match split; ++ inherit (lib) types; ++ inherit (config.security) pass; ++ escapeUnitName = name: ++ lib.concatMapStrings (s: if lib.isList s then "-" else s) ++ (split "[^a-zA-Z0-9_.\\-]+" name); ++in ++{ ++options.security.pass = { ++ store = lib.mkOption { ++ type = types.path; ++ description = '' ++ Default path to the password-store of the orchestrating system. ++ ''; ++ # Does not copy the entire password-store into the Nix store, ++ # only the keys actually used will be. ++ apply = toString; ++ }; ++ secrets = lib.mkOption { ++ default = {}; ++ type = types.attrsOf (types.submodule ({name, config, ...}: { ++ options = { ++ gpg = lib.mkOption { ++ type = types.path; ++ default = builtins.path { ++ path = pass.store + "/${name}.gpg"; ++ name = "${escapeUnitName name}.gpg"; ++ }; ++ description = '' ++ The path to the gnupg-encrypted secret. ++ It will be copied into the Nix store of the orchestrating and of the target system. ++ It must be decipherable by an OpenPGP key within gnupgHome, ++ whose passhrase is on the target system into passphraseFile. ++ Defaults to the name of the secret, prefixed by passwordStore ++ and suffixed by .gpg. ++ ''; ++ }; ++ gnupgHome = lib.mkOption { ++ type = types.str; ++ default = "/root/.gnupg"; ++ description = '' ++ The directory on the target system to the gnupg home ++ used to decrypt the secret. ++ ''; ++ }; ++ passphraseFile = lib.mkOption { ++ type = types.str; ++ default = "/root/key.pass"; ++ description = '' ++ The directory on the target system to a file containing ++ the password of an OpenPGP key in gnupgHome, ++ to which gpg secret is encrypted to. ++ ''; ++ }; ++ mode = lib.mkOption { ++ type = types.str; ++ default = "400"; ++ description = '' ++ Permission mode of the secret path. ++ ''; ++ }; ++ user = lib.mkOption { ++ type = types.str; ++ default = "root"; ++ description = '' ++ Owner of the secret path. ++ ''; ++ }; ++ group = lib.mkOption { ++ type = types.str; ++ default = "root"; ++ description = '' ++ Group of the secret path. ++ ''; ++ }; ++ pipe = lib.mkOption { ++ type = types.nullOr types.str; ++ default = null; ++ description = '' ++ Shell command taking the deciphered secret on its standard input ++ and which must put on its standard output ++ the actual material to be installed. ++ This allows to decorate the secret with non-secret bits. ++ ''; ++ }; ++ path = lib.mkOption { ++ type = types.str; ++ default = name; ++ apply = p: if match "^/.*" p == null then "/run/pass-secrets/"+p+"/file" else p; ++ description = '' ++ The path on the target system where the secret is installed to. ++ Any non-absolute path is relative to /run/pass-secrets. ++ Default to the name of the secret. ++ ''; ++ }; ++ service = lib.mkOption { ++ type = types.str; ++ default = "secret-" + escapeUnitName name + ".service"; ++ description = '' ++ The name of the systemd service. ++ Useful to put constraints like after or wants ++ into services requiring this secret. ++ ''; ++ }; ++ postStart = lib.mkOption { ++ type = types.lines; ++ default = ""; ++ example = "systemctl reload nginx.service"; ++ description = '' ++ Commands to run after new secrets go live. Typically ++ the web server and other servers using secrets need to ++ be reloaded. ++ ''; ++ }; ++ }; ++ })); ++ }; ++}; ++config = lib.mkIf (pass.secrets != {}) { ++ systemd.services = ++ lib.mapAttrs' (target: secret: ++ lib.nameValuePair (lib.removeSuffix ".service" secret.service) { ++ description = "Install secret ${secret.path}"; ++ script = '' ++ set -o pipefail ++ set -eux ++ decrypt() { ++ { ++ ${pkgs.gnupg}/bin/gpg --batch --pinentry-mode loopback \ ++ --passphrase-file '${secret.passphraseFile}' \ ++ --decrypt '${secret.gpg}' \ ++ ${lib.optionalString (secret.pipe != null) (" | "+secret.pipe)} ++ } | ++ install -D -m '${secret.mode}' -o '${secret.user}' -g '${secret.group}' /dev/stdin \ ++ '${secret.path}' ++ } ++ while ! decrypt; do sleep $((1 + ($RANDOM % 10))); done ++ ''; ++ postStart = lib.optionalString (secret.postStart != "") '' ++ set -eux ++ ${secret.postStart} ++ ''; ++ restartIfChanged = true; ++ restartTriggers = [ ++ secret.passphraseFile ++ secret.gpg ++ ]; ++ serviceConfig = { ++ Type = "oneshot"; ++ Environment = "GNUPGHOME=${secret.gnupgHome}"; ++ PrivateTmp = true; ++ RemainAfterExit = true; ++ WorkingDirectory = dirOf secret.gnupgHome; ++ } // lib.optionalAttrs (match "^/.*" target == null) { ++ RuntimeDirectory = lib.removePrefix "/run/" (dirOf secret.path); ++ RuntimeDirectoryMode = "711"; ++ RuntimeDirectoryPreserve = false; ++ }; ++ } ++ ) pass.secrets; ++}; ++meta.maintainers = with lib.maintainers; [ julm ]; ++} diff --git a/nixpkgs/patches/wip.diff b/nixpkgs/patches/transmission+apparmor.diff similarity index 100% rename from nixpkgs/patches/wip.diff rename to nixpkgs/patches/transmission+apparmor.diff diff --git a/shell.nix b/shell.nix index 10ca368..49e6b73 100644 --- a/shell.nix +++ b/shell.nix @@ -36,7 +36,9 @@ let ]; localNixpkgsPatches = [ #/home/julm/src/nix/nixpkgs/wip.patch - nixpkgs/patches/wip.diff + nixpkgs/patches/transmission+apparmor.diff + nixpkgs/patches/installer.ssh-nixos.diff + nixpkgs/patches/security.pass.diff ]; # Build nixpkgs with some patches. nixpkgs = originPkgs.applyPatches { -- 2.49.0 From c97e83e9f4a580b3d6a2eb2e9e2b96789711981b Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Wed, 22 Jul 2020 04:18:42 +0200 Subject: [PATCH 04/16] nix: reorganize a few things --- machines.nix | 23 ++-- nixpkgs/patches/installer.ssh-nixos.diff | 32 +++--- nixpkgs/patches/ssh-nixos.diff | 131 +++++++++++++++++++++++ shell.nix | 26 ++--- shell/modules.nix | 40 ------- 5 files changed, 165 insertions(+), 87 deletions(-) create mode 100644 nixpkgs/patches/ssh-nixos.diff delete mode 100644 shell/modules.nix diff --git a/machines.nix b/machines.nix index 852936a..05a1cb8 100644 --- a/machines.nix +++ b/machines.nix @@ -1,14 +1,13 @@ -with builtins; let - buildMachine = machines: name: config: - (import (config // { - extraArgs = { inherit name machines; } // config.extraArgs; - })); - buildMachines = machines: - let machinesOut = mapAttrs (buildMachine machinesOut) machines; in - mapAttrs (n: system: system.config) machinesOut; -in -buildMachines { - mermet = import machines/mermet.nix; - losurdo = import machines/losurdo.nix; +buildMachine = machines: name: config: + let cfg = if builtins.isPath config then import config else config; in + (import (cfg // { + extraArgs = { inherit name machines; } // config.extraArgs; + })); +buildMachines = machines: + let machinesOut = builtins.mapAttrs (buildMachine machinesOut) machines; in + builtins.mapAttrs (n: system: system.config) machinesOut; +in buildMachines { + mermet = machines/mermet.nix; + losurdo = machines/losurdo.nix; } diff --git a/nixpkgs/patches/installer.ssh-nixos.diff b/nixpkgs/patches/installer.ssh-nixos.diff index d73503c..4064058 100644 --- a/nixpkgs/patches/installer.ssh-nixos.diff +++ b/nixpkgs/patches/installer.ssh-nixos.diff @@ -1,21 +1,21 @@ diff --git a/nixos/modules/installer/ssh-nixos.nix b/nixos/modules/installer/ssh-nixos.nix new file mode 100644 -index 00000000000..52ac88799ee +index 00000000000..6d1b03eea0a --- /dev/null +++ b/nixos/modules/installer/ssh-nixos.nix -@@ -0,0 +1,112 @@ +@@ -0,0 +1,104 @@ +{ pkgs, lib, config, ... }: +let + inherit (lib) types; + inherit (config) networking; + cfg = config.installer.ssh-nixos; + nixRunDefaultCommand = "bash"; ++ # Wraps ssh so that nix copy or calls to ssh added to cfg.script ++ # use cfg.sshFlags and $SSH_FLAGS. + ssh = pkgs.writeShellScriptBin "ssh" '' + set -eu + PATH=$OLDPATH -+ set -x -+ ssh -l '${cfg.login}' \ -+ ${lib.escapeShellArgs cfg.sshFlags} ''${SSH_FLAGS:-} "$@" ++ ssh ${lib.escapeShellArgs cfg.sshFlags} ''${SSH_FLAGS:-} "$@" + ''; +in +{ @@ -32,18 +32,16 @@ index 00000000000..52ac88799ee + example = '' + lib.mkBefore '''''' + gpg --decrypt initrd/ssh.key.gpg | -+ ssh root@''${config.installer.ssh-nixos.target} \ ++ ssh ''${config.installer.ssh-nixos.target} \ + install -D -m 400 -o root -g root /dev/stdin /root/initrd/ssh.key + ''''''; + ''; + description = '' -+ Install script copying the configured NixOS to the target ++ Install script copying through SSH the configured NixOS system ++ to the target + and switching to the new configuration. -+ It is made available here for prepending or appending commands ++ This option is made available here for prepending or appending commands + with the usual mkBefore and mkAfter. -+ In case you run it often or add multiple ssh calls to it, -+ consider configuring the OpenSSH client with ControlMaster auto -+ to keep the SSH connexion alive between calls to literal. + + This script is usually run with: + @@ -68,17 +66,11 @@ index 00000000000..52ac88799ee + ${script} + ''; + }; -+ login = lib.mkOption { -+ type = types.str; -+ default = "root"; -+ example = "admin"; -+ description = "Login name passed to ssh."; -+ }; + target = lib.mkOption { + type = types.str; -+ default = "${networking.hostName}.${networking.domain}"; -+ example = "192.168.1.10"; -+ description = "Destination where to install NixOS passed to ssh."; ++ default = "root@${networking.hostName}.${networking.domain}"; ++ example = "root@192.168.1.10"; ++ description = "SSH destination where to install NixOS."; + }; + sshFlags = lib.mkOption { + type = types.listOf types.str; diff --git a/nixpkgs/patches/ssh-nixos.diff b/nixpkgs/patches/ssh-nixos.diff new file mode 100644 index 0000000..059c3a2 --- /dev/null +++ b/nixpkgs/patches/ssh-nixos.diff @@ -0,0 +1,131 @@ +diff --git a/nixos/modules/installer/ssh-nixos.nix b/nixos/modules/installer/ssh-nixos.nix +new file mode 100644 +index 00000000000..2822c8814c0 +--- /dev/null ++++ b/nixos/modules/installer/ssh-nixos.nix +@@ -0,0 +1,113 @@ ++{ pkgs, lib, config, ... }: ++let ++ inherit (lib) types; ++ inherit (config) networking; ++ cfg = config.installer.ssh-nixos; ++ nixRunDefaultCommand = "bash"; ++ ssh = pkgs.writeShellScriptBin "ssh" '' ++ set -eu ++ PATH=$OLDPATH ++ set -x ++ ssh -l '${cfg.login}' \ ++ ${lib.escapeShellArgs cfg.sshFlags} ''${SSH_FLAGS:-} "$@" ++ ''; ++in ++{ ++options.installer.ssh-nixos = { ++ PATH = lib.mkOption { ++ type = types.listOf types.package; ++ default = []; ++ apply = lib.makeBinPath; ++ description = "Packages to be appended to the PATH of the script."; ++ }; ++ script = lib.mkOption { ++ type = types.lines; ++ default = ""; ++ example = '' ++ lib.mkBefore '''''' ++ gpg --decrypt initrd/ssh.key.gpg | ++ ssh root@''${config.installer.ssh-nixos.target} \ ++ install -D -m 400 -o root -g root /dev/stdin /root/initrd/ssh.key ++ ''''''; ++ ''; ++ description = '' ++ Install script copying the configured NixOS via SSH ++ to the target ++ and switching to the new configuration. ++ It is made available here for prepending or appending commands ++ with the usual mkBefore and mkAfter. ++ In case you run it often or add multiple ssh calls to it, ++ consider configuring the OpenSSH client with ControlMaster auto ++ to keep the SSH connexion alive between calls to literal. ++ ++ This script is usually run with: ++ ++ $ nix run system.config.installer.ssh-nixos -f nixos.nix ++ ++ where nixos.nix can be: ++ ++ import { ++ system = "x86_64-linux"; ++ configuration = { config, lib, pkgs }: { ++ # Your usual configuration.nix content can go here ++ }; ++ } ++ ++ ''; ++ apply = script: pkgs.writeShellScriptBin nixRunDefaultCommand '' ++ set -eu ++ set -o pipefail ++ export OLDPATH=$PATH:${cfg.PATH} ++ PATH="${ssh}/bin:$OLDPATH" ++ set -x ++ ${script} ++ ''; ++ }; ++ login = lib.mkOption { ++ type = types.str; ++ default = "root"; ++ example = "admin"; ++ description = "Login name passed to ssh."; ++ }; ++ target = lib.mkOption { ++ type = types.str; ++ default = "${networking.hostName}.${networking.domain}"; ++ example = "192.168.1.10"; ++ description = "Destination where to install NixOS passed to ssh."; ++ }; ++ sshFlags = lib.mkOption { ++ type = types.listOf types.str; ++ default = ["-o" "ControlMaster=auto"]; ++ description = '' ++ Extra flags passed to ssh. ++ Environment variable SSH_FLAGS can also be used at runtime. ++ ''; ++ }; ++ nixCopyFlags = lib.mkOption { ++ type = types.listOf types.str; ++ default = ["--substitute-on-destination"]; ++ description = '' ++ Extra flags passed to nix copy. ++ Environment variable NIX_COPY_FLAGS can also be used at runtime. ++ ''; ++ }; ++ profile = lib.mkOption { ++ type = types.str; ++ default = "/nix/var/nix/profiles/system"; ++ }; ++}; ++config = { ++ installer.ssh-nixos.PATH = with pkgs; [nix openssh]; ++ installer.ssh-nixos.script = ++ let nixos = config.system.build.toplevel; in '' ++ nix ''${NIX_FLAGS:-} copy \ ++ --to ssh://'${cfg.target}' \ ++ ${lib.escapeShellArgs cfg.nixCopyFlags} ''${NIX_COPY_FLAGS:-} \ ++ ${nixos} ++ ssh '${cfg.target}' \ ++ nix-env --profile '${cfg.profile}' --set '${nixos}' '&&' \ ++ '${cfg.profile}'/bin/switch-to-configuration "''${NIXOS_SWITCH:-switch}" ++ ''; ++}; ++meta.maintainers = [ lib.maintainers.julm ]; ++} +diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix +index f361163ca63..15659fde11b 100644 +--- a/nixos/modules/module-list.nix ++++ b/nixos/modules/module-list.nix +@@ -80,6 +80,7 @@ + ./i18n/input-method/ibus.nix + ./i18n/input-method/nabi.nix + ./i18n/input-method/uim.nix ++ ./installer/ssh-nixos.nix + ./installer/tools/tools.nix + ./misc/assertions.nix + ./misc/crashdump.nix diff --git a/shell.nix b/shell.nix index 49e6b73..45c9e78 100644 --- a/shell.nix +++ b/shell.nix @@ -35,7 +35,6 @@ let */ ]; localNixpkgsPatches = [ - #/home/julm/src/nix/nixpkgs/wip.patch nixpkgs/patches/transmission+apparmor.diff nixpkgs/patches/installer.ssh-nixos.diff nixpkgs/patches/security.pass.diff @@ -58,11 +57,9 @@ let overlays = import nixpkgs/overlays.nix; }; - lib = pkgs.lib; - nixos = pkgs.nixos {}; # Configuration of shell/modules/ # to expand shellHook and buildInputs of this shell.nix - configuration = {config, ...}: { + shellConfig = {config, ...}: { imports = [ shell/gnupg.nix ]; @@ -110,11 +107,10 @@ let # Using modules enables to separate specific configurations # from reusable code in shell/modules.nix and shell/modules/ # which may find its way in another git repository one day. - modules = - (import shell/modules.nix { - inherit pkgs lib; - modules = [ configuration ]; - }).config; + shell = (pkgs.lib.evalModules { + modules = [ shellConfig ] ++ map import (pkgs.lib.findFiles ".*\\.nix" shell/modules); + args = { inherit pkgs; }; + }).config; pwd = toString (./. + ""); sourcephile-shred-tmp = pkgs.writeShellScriptBin "sourcephile-shred-tmp" '' @@ -132,11 +128,11 @@ pkgs.mkShell { src = null; #preferLocalBuild = true; #allowSubstitutes = false; - buildInputs = modules.nix-shell.buildInputs ++ [ + buildInputs = shell.nix-shell.buildInputs ++ [ sourcephile-shred-tmp - nixos.nixos-generate-config - nixos.nixos-install - nixos.nixos-enter + (pkgs.nixos []).nixos-generate-config + (pkgs.nixos []).nixos-install + (pkgs.nixos []).nixos-enter #pkgs.binutils pkgs.coreutils pkgs.cryptsetup @@ -196,7 +192,7 @@ pkgs.mkShell { # Nix PATH=$NIX_SHELL_PATH:$PATH - export NIX_PATH="${lib.concatStringsSep ":" [ + export NIX_PATH="${pkgs.lib.concatStringsSep ":" [ "machines=$PWD/machines.nix" #"pass=$PASSWORD_STORE_DIR" "nixpkgs=${toString pkgs.path}" @@ -209,7 +205,7 @@ pkgs.mkShell { # hence shred at startup, which is not ideal. sourcephile-shred-tmp - ${modules.nix-shell.shellHook} + ${shell.nix-shell.shellHook} # gpg export GPG_TTY=$(tty) diff --git a/shell/modules.nix b/shell/modules.nix deleted file mode 100644 index c838ffe..0000000 --- a/shell/modules.nix +++ /dev/null @@ -1,40 +0,0 @@ -{ pkgs -, lib ? pkgs.lib -, modules ? [] -, extraArgs ? {} -, specialArgs ? {} -, check ? true -, prefix ? [] -}: -let extraArgs_ = extraArgs; - pkgs_ = pkgs; - baseModules = map import (lib.findFiles ".*\\.nix" ./modules ); - pkgsModule = rec { - _file = ./modules.nix; - key = _file; - config = { - _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_); - }; - }; -in -rec { - # Merge the option definitions in all modules, - # forming the full system configuration. - inherit (lib.evalModules { - inherit prefix; - inherit check; - modules = modules ++ baseModules ++ [ pkgsModule ]; - args = extraArgs; - inherit specialArgs; - #specialArgs = { modulesPath = config/modules.nix; } // specialArgs; - }) config options; - - # These are the extra arguments passed to every module. - # In particular, Nixpkgs is passed through the "pkgs" argument. - extraArgs = extraArgs_ // { - inherit modules; - inherit baseModules; - }; - - inherit (config._module.args) pkgs; -} -- 2.49.0 From f732b207284504c39b52205fb000cd0be8ac5e09 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Wed, 22 Jul 2020 15:26:32 +0200 Subject: [PATCH 05/16] nix: simplify the sending root's OpenPGP key --- machines.nix | 8 ++-- machines/losurdo/networking.nix | 9 ++--- machines/losurdo/security.nix | 42 +++++++------------- machines/losurdo/system.nix | 2 + machines/losurdo/users.nix | 1 + machines/mermet.nix | 1 + machines/mermet/knot/sourcephile.fr.nix | 1 + machines/mermet/networking.nix | 9 ++--- machines/mermet/nginx/sourcephile.fr/git.nix | 2 +- machines/mermet/security.nix | 41 +++++++------------ nixos/modules.nix | 1 - nixpkgs/patches/security.pass.diff | 24 ++++++++--- shell.nix | 2 + shell/gnupg.nix | 22 ++-------- 14 files changed, 72 insertions(+), 93 deletions(-) diff --git a/machines.nix b/machines.nix index 05a1cb8..f26c2d1 100644 --- a/machines.nix +++ b/machines.nix @@ -1,12 +1,12 @@ let -buildMachine = machines: name: config: +buildMachine = machines: machineName: config: let cfg = if builtins.isPath config then import config else config; in (import (cfg // { - extraArgs = { inherit name machines; } // config.extraArgs; + extraArgs = { inherit machineName machines; } // cfg.extraArgs; })); buildMachines = machines: - let machinesOut = builtins.mapAttrs (buildMachine machinesOut) machines; in - builtins.mapAttrs (n: system: system.config) machinesOut; + let machinesEval = builtins.mapAttrs (buildMachine machinesEval) machines; in + builtins.mapAttrs (n: eval: eval.config) machinesEval; in buildMachines { mermet = machines/mermet.nix; losurdo = machines/losurdo.nix; diff --git a/machines/losurdo/networking.nix b/machines/losurdo/networking.nix index 475ea3c..e701012 100644 --- a/machines/losurdo/networking.nix +++ b/machines/losurdo/networking.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, name, ... }: +{ pkgs, lib, config, machineName, ... }: with builtins; let inherit (builtins.extraBuiltins) pass-to-file; @@ -88,10 +88,9 @@ boot.kernel.sysctl = { "net.ipv6.conf.enp5s0.disable_ipv6" = 1; }; -networking = rec { - hostName = name; - domainBase = "sourcephile"; - domain = "${domainBase}.fr"; +networking = { + hostName = machineName; + domain = "sourcephile.fr"; useDHCP = false; defaultGateway = { diff --git a/machines/losurdo/security.nix b/machines/losurdo/security.nix index 5927bd2..c3c0bac 100644 --- a/machines/losurdo/security.nix +++ b/machines/losurdo/security.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machineName, ... }: let inherit (config.security) pass; rootKey = "root/key"; @@ -8,38 +8,26 @@ in imports = [ ]; -security.pass = { - store = ../../../sec/pass/machines/losurdo; - secrets."${rootKey}" = { - gpg = ../../../sec/gnupg/machines/losurdo/root/key.gpg; - # Symmetrically decrypt and load the rootKey into root's gnupg secret keyring. - postStart = '' - set -x - ${pkgs.gnupg}/bin/gpg --batch --pinentry-mode loopback \ - --passphrase-file /${rootKey}.pass \ - --import '${pass.secrets."${rootKey}".path}' - shred -u '${pass.secrets."${rootKey}".path}' - ''; - }; -}; +security.pass.store = ../../../sec/pass/machines/losurdo; installer.ssh-nixos = { PATH = with pkgs; [gnupg openssh]; - # Decrypt the rootKey passphrase and the initrd SSH host key - # and send them to the target host. script = lib.mkBefore '' + # Send the rootKey's passphrase + gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass + + # Send the rootKey gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | - ssh '${config.installer.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass + gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | + ssh '${config.installer.ssh-nixos.target}' \ + gpg --batch --pinentry-mode loopback --passphrase-file /root/key.pass --import + + # Send the SSH key of the initrd gpg --decrypt '${pass.store}/${initrdKey}.gpg' | - ssh '${config.installer.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} + ssh '${config.installer.ssh-nixos.target}' \ + install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} ''; }; boot.initrd.network.ssh.hostKeys = [ "/root/${initrdKey}" ]; -systemd.services = lib.mapAttrs' (target: secret: - # Start the rootKey service before the other services decrypting secrets. - lib.nameValuePair (lib.removeSuffix ".service" secret.service) - (lib.optionalAttrs (target != "${rootKey}") { - after = [ pass.secrets."${rootKey}".service ]; - wants = [ pass.secrets."${rootKey}".service ]; - }) - ) pass.secrets; } diff --git a/machines/losurdo/system.nix b/machines/losurdo/system.nix index 7002738..c1bf4f8 100644 --- a/machines/losurdo/system.nix +++ b/machines/losurdo/system.nix @@ -13,6 +13,8 @@ system.stateVersion = "19.09"; # Did you read the comment? # and let mosh work smoothly. services.logind.killUserProcesses = false; +nix.gc.dates = "daily"; +nix.gc.options = "--delete-older-than 7d"; services.unbound.enable = true; diff --git a/machines/losurdo/users.nix b/machines/losurdo/users.nix index ffce35f..7e85cdd 100644 --- a/machines/losurdo/users.nix +++ b/machines/losurdo/users.nix @@ -17,6 +17,7 @@ networking.nftables.ruleset = '' add rule inet filter fw2net tcp dport 43 skuid ${users.julm.name} counter accept comment "Whois" add rule inet filter fw2net tcp dport 6697 skuid ${users.julm.name} counter accept comment "IRCS" add rule inet filter fw2net tcp dport 11371 skuid ${users.julm.name} counter accept comment "HKP" + add rule inet filter fw2net tcp dport {9009,9010,9011,9012,9013} skuid ${users.julm.name} counter accept comment "croc" ''; users = { diff --git a/machines/mermet.nix b/machines/mermet.nix index c8a9820..346400e 100644 --- a/machines/mermet.nix +++ b/machines/mermet.nix @@ -17,6 +17,7 @@ modules = [ ../nixos/defaults.nix ../nixos/profiles/services/unbound.nix mermet/acme.nix + mermet/croc.nix mermet/debug.nix mermet/dovecot.nix mermet/fail2ban.nix diff --git a/machines/mermet/knot/sourcephile.fr.nix b/machines/mermet/knot/sourcephile.fr.nix index 4fb3877..43228b4 100644 --- a/machines/mermet/knot/sourcephile.fr.nix +++ b/machines/mermet/knot/sourcephile.fr.nix @@ -101,6 +101,7 @@ services.knot.zones."${domain}" = { lemoutona5pattes A ${machines.mermet.extraArgs.ipv4} covid19 A ${machines.mermet.extraArgs.ipv4} openconcerto A ${machines.losurdo.extraArgs.ipv4} + croc A ${machines.mermet.extraArgs.ipv4} ; SPF (Sender Policy Framework) @ 3600 IN SPF "v=spf1 mx ip4:${machines.mermet.extraArgs.ipv4} -all" diff --git a/machines/mermet/networking.nix b/machines/mermet/networking.nix index 54ae0af..e35c4a6 100644 --- a/machines/mermet/networking.nix +++ b/machines/mermet/networking.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, name, ipv4, machines, ... }: +{ pkgs, lib, config, machineName, ipv4, machines, ... }: with builtins; let inherit (builtins.extraBuiltins) pass-to-file; @@ -104,10 +104,9 @@ services.knot.extraConfig = lib.mkBefore '' #listen: ::@53 ''; -networking = rec { - hostName = name; - domainBase = "sourcephile"; - domain = "${domainBase}.fr"; +networking = { + hostName = machineName; + domain = "sourcephile.fr"; useDHCP = false; defaultGateway = { diff --git a/machines/mermet/nginx/sourcephile.fr/git.nix b/machines/mermet/nginx/sourcephile.fr/git.nix index d31b6e5..cb30143 100644 --- a/machines/mermet/nginx/sourcephile.fr/git.nix +++ b/machines/mermet/nginx/sourcephile.fr/git.nix @@ -104,7 +104,7 @@ services.gitweb = { extraConfig = '' use utf8; my $s = $cgi->https() ? "s" : ""; - @extra_breadcrumbs = (["${networking.domainBase}" => "http''${s}://${domain}"]); + @extra_breadcrumbs = (["sourcephile" => "http''${s}://${domain}"]); $site_name = "Git — Sourcephile"; $home_link_str = "git"; $projects_list = "${gitolite.dataDir}/projects.list"; diff --git a/machines/mermet/security.nix b/machines/mermet/security.nix index ad9c83a..c3c0bac 100644 --- a/machines/mermet/security.nix +++ b/machines/mermet/security.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machineName, ... }: let inherit (config.security) pass; rootKey = "root/key"; @@ -8,37 +8,26 @@ in imports = [ ]; -security.pass = { - store = ../../../sec/pass/machines/mermet; - secrets."${rootKey}" = { - gpg = ../../../sec/gnupg/machines/mermet/root/key.gpg; - # Symmetrically decrypt and load the rootKey into root's gnupg secret keyring. - postStart = '' - ${pkgs.gnupg}/bin/gpg --batch --pinentry-mode loopback \ - --passphrase-file /${rootKey}.pass \ - --import '${pass.secrets."${rootKey}".path}' - shred -u '${pass.secrets."${rootKey}".path}' - ''; - }; -}; +security.pass.store = ../../../sec/pass/machines/losurdo; installer.ssh-nixos = { PATH = with pkgs; [gnupg openssh]; - # Decrypt the rootKey passphrase and the initrd SSH host key - # and send them to the target host. script = lib.mkBefore '' + # Send the rootKey's passphrase + gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass + + # Send the rootKey gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | - ssh '${config.installer.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass + gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | + ssh '${config.installer.ssh-nixos.target}' \ + gpg --batch --pinentry-mode loopback --passphrase-file /root/key.pass --import + + # Send the SSH key of the initrd gpg --decrypt '${pass.store}/${initrdKey}.gpg' | - ssh '${config.installer.ssh-nixos.target}' install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} + ssh '${config.installer.ssh-nixos.target}' \ + install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} ''; }; boot.initrd.network.ssh.hostKeys = [ "/root/${initrdKey}" ]; -systemd.services = lib.mapAttrs' (target: secret: - # Start the rootKey service before the other services decrypting secrets. - lib.nameValuePair (lib.removeSuffix ".service" secret.service) - (lib.optionalAttrs (target != "${rootKey}") { - after = [ pass.secrets."${rootKey}".service ]; - wants = [ pass.secrets."${rootKey}".service ]; - }) - ) pass.secrets; } diff --git a/nixos/modules.nix b/nixos/modules.nix index eed873b..089698c 100644 --- a/nixos/modules.nix +++ b/nixos/modules.nix @@ -3,7 +3,6 @@ # its clearer, safer and more flexible if not quicker. { imports = [ - modules/services/networking/domains.nix modules/services/databases/openldap.nix modules/services/mail/public-inbox.nix #modules/services/mail/mlmmj.nix diff --git a/nixpkgs/patches/security.pass.diff b/nixpkgs/patches/security.pass.diff index 862b2c1..220df8c 100644 --- a/nixpkgs/patches/security.pass.diff +++ b/nixpkgs/patches/security.pass.diff @@ -12,10 +12,10 @@ index f361163ca63..5e306de7dad 100644 ./security/pam_mount.nix diff --git a/nixos/modules/security/pass.nix b/nixos/modules/security/pass.nix new file mode 100644 -index 00000000000..a2141d56d16 +index 00000000000..bf72d06e1ea --- /dev/null +++ b/nixos/modules/security/pass.nix -@@ -0,0 +1,165 @@ +@@ -0,0 +1,179 @@ +{ pkgs, lib, config, ... }: +let + inherit (builtins) dirOf head listToAttrs match split; @@ -127,9 +127,18 @@ index 00000000000..a2141d56d16 + default = ""; + example = "systemctl reload nginx.service"; + description = '' -+ Commands to run after new secrets go live. Typically -+ the web server and other servers using secrets need to -+ be reloaded. ++ Commands to run after new secrets go live. ++ Typically the web server and other servers using secrets ++ need to be reloaded. ++ ''; ++ }; ++ postStop = lib.mkOption { ++ type = types.lines; ++ default = ""; ++ example = ''shred -u "$secret_file"''; ++ description = '' ++ Commands to run after stopping the service. ++ Typically removing a persistent secret. + ''; + }; + }; @@ -160,6 +169,11 @@ index 00000000000..a2141d56d16 + set -eux + ${secret.postStart} + ''; ++ postStop = lib.optionalString (secret.postStop != "") '' ++ secret_file='${secret.path}' ++ set -eux ++ ${secret.postStop} ++ ''; + restartIfChanged = true; + restartTriggers = [ + secret.passphraseFile diff --git a/shell.nix b/shell.nix index 45c9e78..3827a18 100644 --- a/shell.nix +++ b/shell.nix @@ -38,6 +38,7 @@ let nixpkgs/patches/transmission+apparmor.diff nixpkgs/patches/installer.ssh-nixos.diff nixpkgs/patches/security.pass.diff + nixpkgs/patches/services.croc.diff ]; # Build nixpkgs with some patches. nixpkgs = originPkgs.applyPatches { @@ -182,6 +183,7 @@ pkgs.mkShell { pkgs.strace pkgs.utillinux #pkgs.zfstools + pkgs.linuxPackages.perf ]; #enableParallelBuilding = true; shellHook = '' diff --git a/shell/gnupg.nix b/shell/gnupg.nix index 8c1bcbd..ee43a75 100644 --- a/shell/gnupg.nix +++ b/shell/gnupg.nix @@ -29,31 +29,15 @@ gnupg.keys = { }; } // lib.listToAttrs ( let domain = "sourcephile.fr"; in - builtins.map (srv: lib.nameValuePair "root@${srv}.${domain}" { - uid = "root@${srv}.${domain}"; + builtins.map (machine: lib.nameValuePair "root@${machine}.${domain}" { + uid = "root@${machine}.${domain}"; algo = "rsa4096"; expire = "0"; usage = ["cert" "sign"]; - passPath = "machines/${srv}/root/key.pass"; + passPath = "machines/${machine}/root/key.pass"; subKeys = [ { algo = "rsa4096"; expire = "0"; usage = ["encrypt"]; } ]; backupRecipients = [""]; - # This encrypt subkey is put into a root/key.gpg, and then on the Nix stores, - # to decrypt machines."${srv}".security.pass.secrets . - # Its passphrase in root/key.pass is decrypted and sent by ssh before each call to nix copy - # by adding to machines."${srv}".install.nixos-ssh.script . - postRun = '' - info " generate $GNUPGHOME/machines/${srv}/root/key.gpg" - test -s "$GNUPGHOME/machines/${srv}/root/key.gpg" || { - mkdir -p "$GNUPGHOME/machines/${srv}/root" - ${pkgs.gnupg}/bin/gpg --batch --pinentry-mode loopback --export-secret-keys --armor \ - --passphrase-fd 3 3< <(${pkgs.gnupg}/bin/gpg --decrypt "$PASSWORD_STORE_DIR/machines/${srv}/root/key.pass.gpg") \ - --export-options export-minimal @root@${srv}.${domain} | - ${pkgs.gnupg}/bin/gpg --symmetric --batch --pinentry-mode loopback \ - --passphrase-fd 3 3< <(${pkgs.gnupg}/bin/gpg --decrypt "$PASSWORD_STORE_DIR/machines/${srv}/root/key.pass.gpg") \ - --output "$GNUPGHOME/machines/${srv}/root/key.gpg" - } - ''; }) (builtins.attrNames (import ../machines.nix))); } -- 2.49.0 From 2b302b2ce183ea0344dc3babf77c092d1c62d135 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Fri, 24 Jul 2020 09:51:00 +0200 Subject: [PATCH 06/16] nix: update nixpkgs/patches --- machines.nix | 24 +- machines/losurdo/acme/autogeree.net.nix | 10 +- machines/losurdo/acme/sourcephile.fr.nix | 10 +- .../losurdo/nginx/sourcephile.fr/losurdo.nix | 10 +- machines/losurdo/postgresql/openconcerto.nix | 10 +- machines/losurdo/security.nix | 56 +++- machines/losurdo/syncoid.nix | 4 +- machines/losurdo/system.nix | 1 - machines/losurdo/transmission.nix | 10 +- machines/losurdo/users.nix | 2 +- machines/mermet/croc.nix | 14 + machines/mermet/knot/autogeree.net.nix | 11 +- machines/mermet/knot/sourcephile.fr.nix | 11 +- machines/mermet/rspamd.nix | 10 +- machines/mermet/rspamd/autogeree.net.nix | 8 +- machines/mermet/rspamd/sourcephile.fr.nix | 8 +- machines/mermet/security.nix | 49 ++-- nixos/modules/security/pass.nix | 165 ----------- nixpkgs/patches/security.gnupg.diff | 270 ++++++++++++++++++ nixpkgs/patches/security.pass.diff | 85 ++++-- nixpkgs/patches/services.croc.diff | 96 +++++++ nixpkgs/patches/ssh-nixos.diff | 131 --------- shell.nix | 10 +- shell/modules/tools/security/gnupg.nix | 29 +- 24 files changed, 599 insertions(+), 435 deletions(-) create mode 100644 machines/mermet/croc.nix delete mode 100644 nixos/modules/security/pass.nix create mode 100644 nixpkgs/patches/security.gnupg.diff create mode 100644 nixpkgs/patches/services.croc.diff delete mode 100644 nixpkgs/patches/ssh-nixos.diff diff --git a/machines.nix b/machines.nix index f26c2d1..b555a3b 100644 --- a/machines.nix +++ b/machines.nix @@ -1,13 +1,11 @@ -let -buildMachine = machines: machineName: config: - let cfg = if builtins.isPath config then import config else config; in - (import (cfg // { - extraArgs = { inherit machineName machines; } // cfg.extraArgs; - })); -buildMachines = machines: - let machinesEval = builtins.mapAttrs (buildMachine machinesEval) machines; in - builtins.mapAttrs (n: eval: eval.config) machinesEval; -in buildMachines { - mermet = machines/mermet.nix; - losurdo = machines/losurdo.nix; -} +let machines = builtins.mapAttrs (machineName: machineConfig: + let cfg = import machineConfig; in + import (cfg // { + extraArgs = { inherit machineName machines; } // (cfg.extraArgs or {}); + })) { + +mermet = machines/mermet.nix; +losurdo = machines/losurdo.nix; + +}; +in builtins.mapAttrs (n: c: c.config) machines diff --git a/machines/losurdo/acme/autogeree.net.nix b/machines/losurdo/acme/autogeree.net.nix index 5fe8503..3102b97 100644 --- a/machines/losurdo/acme/autogeree.net.nix +++ b/machines/losurdo/acme/autogeree.net.nix @@ -2,7 +2,7 @@ let domain = "autogeree.net"; domainID = lib.replaceStrings ["."] ["_"] domain; - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.users) users groups; in { @@ -30,9 +30,9 @@ security.acme.certs."${domain}" = { # ns6.gandi.net takes roughly 5min to update # hence lego's RFC2136_PROPAGATION_TIMEOUT=1000 #dnsPropagationCheck = false; - credentialsFile = pass.secrets."lego/${domain}/rfc2136".path; + credentialsFile = gnupg.secrets."lego/${domain}/rfc2136".path; }; -security.pass.secrets."lego/${domain}/rfc2136" = { +security.gnupg.secrets."lego/${domain}/rfc2136" = { pipe = '' cat - ${pkgs.writeText "env" '' RFC2136_NAMESERVER=ns.${domain}:53 @@ -49,11 +49,11 @@ security.pass.secrets."lego/${domain}/rfc2136" = { systemd.services."acme-${domain}" = { after = [ "unbound.service" - pass.secrets."lego/${domain}/rfc2136".service + gnupg.secrets."lego/${domain}/rfc2136".service ]; wants = [ "unbound.service" - pass.secrets."lego/${domain}/rfc2136".service + gnupg.secrets."lego/${domain}/rfc2136".service ]; }; } diff --git a/machines/losurdo/acme/sourcephile.fr.nix b/machines/losurdo/acme/sourcephile.fr.nix index 0fcd176..a2260e6 100644 --- a/machines/losurdo/acme/sourcephile.fr.nix +++ b/machines/losurdo/acme/sourcephile.fr.nix @@ -2,7 +2,7 @@ let domain = "sourcephile.fr"; domainID = lib.replaceStrings ["."] ["_"] domain; - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.users) users groups; in { @@ -27,9 +27,9 @@ security.acme.certs."${domain}" = { # ns6.gandi.net takes roughly 5min to update # hence lego's RFC2136_PROPAGATION_TIMEOUT=1000 #dnsPropagationCheck = false; - credentialsFile = pass.secrets."lego/${domain}/rfc2136".path; + credentialsFile = gnupg.secrets."lego/${domain}/rfc2136".path; }; -security.pass.secrets."lego/${domain}/rfc2136" = { +security.gnupg.secrets."lego/${domain}/rfc2136" = { pipe = '' cat - ${pkgs.writeText "env" '' RFC2136_NAMESERVER=ns.${domain}:53 @@ -46,11 +46,11 @@ security.pass.secrets."lego/${domain}/rfc2136" = { systemd.services."acme-${domain}" = { after = [ "unbound.service" - pass.secrets."lego/${domain}/rfc2136".service + gnupg.secrets."lego/${domain}/rfc2136".service ]; wants = [ "unbound.service" - pass.secrets."lego/${domain}/rfc2136".service + gnupg.secrets."lego/${domain}/rfc2136".service ]; }; } diff --git a/machines/losurdo/nginx/sourcephile.fr/losurdo.nix b/machines/losurdo/nginx/sourcephile.fr/losurdo.nix index 4886e5b..6b76546 100644 --- a/machines/losurdo/nginx/sourcephile.fr/losurdo.nix +++ b/machines/losurdo/nginx/sourcephile.fr/losurdo.nix @@ -2,7 +2,7 @@ { pkgs, lib, config, ... }: let inherit (config) networking; - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.services) nginx; srv = "losurdo"; in @@ -31,17 +31,17 @@ services.nginx = { ''; locations."/sevy".extraConfig = '' auth_basic "sevy's area"; - auth_basic_user_file ${pass.secrets."nginx/sevy/htpasswd".path}; + auth_basic_user_file ${gnupg.secrets."nginx/sevy/htpasswd".path}; autoindex off; ''; }; }; systemd.services.nginx = { serviceConfig.LogsDirectory = lib.mkForce ["nginx/${domain}/${srv}"]; - wants = [ pass.secrets."nginx/sevy/htpasswd".service ]; - after = [ pass.secrets."nginx/sevy/htpasswd".service ]; + wants = [ gnupg.secrets."nginx/sevy/htpasswd".service ]; + after = [ gnupg.secrets."nginx/sevy/htpasswd".service ]; }; -security.pass.secrets."nginx/sevy/htpasswd" = { +security.gnupg.secrets."nginx/sevy/htpasswd" = { # Generated with: echo "$user:$(openssl passwd -apr1)" user = nginx.user; group = nginx.group; diff --git a/machines/losurdo/postgresql/openconcerto.nix b/machines/losurdo/postgresql/openconcerto.nix index 1da4bea..45271b5 100644 --- a/machines/losurdo/postgresql/openconcerto.nix +++ b/machines/losurdo/postgresql/openconcerto.nix @@ -5,7 +5,7 @@ let url = "https://www.openconcerto.org/fr/telechargement/1.6/OpenConcerto-1.6.3.sql.zip"; sha256 = "02h35ni9xknzrjsra56c3zhlhs0ji9qc61kcgi7vgcpylqjw0s6n"; }; - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.users) users groups; inherit (config) networking; # Example of ~/.config/OpenConcerto/main.properties @@ -44,10 +44,10 @@ services.postgresql = { user ${owner} ${db} ''; }; -security.pass.secrets."postgresql/pass/${owner}" = {}; +security.gnupg.secrets."postgresql/pass/${owner}" = {}; systemd.services.postgresql = { - after = [ pass.secrets."postgresql/pass/${owner}".service ]; - wants = [ pass.secrets."postgresql/pass/${owner}".service ]; + after = [ gnupg.secrets."postgresql/pass/${owner}".service ]; + wants = [ gnupg.secrets."postgresql/pass/${owner}".service ]; postStart = lib.mkAfter '' sed -e 's/ \(TO\|FROM\) \+openconcerto/ \1 ${owner}/g' \ ${sql}/OpenConcerto-1.6.3.sql | @@ -56,7 +56,7 @@ systemd.services.postgresql = { lc_collate=fr_FR.UTF-8 \ lc_type=fr_FR.UTF-8 \ owner=${owner} \ - pass=$(cat ${pass.secrets."postgresql/pass/${owner}".path}) \ + pass=$(cat ${gnupg.secrets."postgresql/pass/${owner}".path}) \ pg_createdb ${db} >/dev/null $PSQL -d "${db}" -AqtX --set ON_ERROR_STOP=1 -f - < ]; -security.pass.store = ../../../sec/pass/machines/losurdo; +security.gnupg.store = builtins.getEnv "PASSWORD_STORE_DIR" + "/machines/${machineName}"; +services.openssh.extraConfig = '' + StreamLocalBindUnlink yes +''; installer.ssh-nixos = { - PATH = with pkgs; [gnupg openssh]; - script = lib.mkBefore '' - # Send the rootKey's passphrase - gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | - ssh '${config.installer.ssh-nixos.target}' \ - install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass + PATH = [pkgs.gnupg pkgs.openssh]; + sshFlags = [ + #"-R" "/var/lib/gnupg/S.gpg-agent.extra:/run/user/1000/gnupg/d.w1sj57hx3zfcwadyxpr6wko9/S.gpg-agent.extra" + #"-o" "StreamLocalBindUnlink=yes" + ]; + script = lib.mkMerge [ + (lib.mkBefore '' + # Send the SSH key of the initrd + gpg --decrypt '${gnupg.store}/${initrdKey}.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} + '') + (lib.mkBefore '' + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts '&&' \ + gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import || true + # Send the rootKey's passphrase + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts '&&' \ + gpg-preset-passphrase \ + --homedir /var/lib/gnupg \ + --preset ${keygrip} || true + '') + ]; + /* # Send the rootKey - gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | ssh '${config.installer.ssh-nixos.target}' \ - gpg --batch --pinentry-mode loopback --passphrase-file /root/key.pass --import + gpg --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import + + gpg --batch --export @root@${machineName} | + ssh '${config.installer.ssh-nixos.target}' \ + gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import + */ - # Send the SSH key of the initrd - gpg --decrypt '${pass.store}/${initrdKey}.gpg' | - ssh '${config.installer.ssh-nixos.target}' \ - install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} - ''; }; boot.initrd.network.ssh.hostKeys = [ "/root/${initrdKey}" ]; } diff --git a/machines/losurdo/syncoid.nix b/machines/losurdo/syncoid.nix index 968d358..b1d07f1 100644 --- a/machines/losurdo/syncoid.nix +++ b/machines/losurdo/syncoid.nix @@ -1,10 +1,10 @@ { pkgs, lib, config, ... }: -let inherit (config.security) pass; in +let inherit (config.security) gnupg; in { services.syncoid = { enable = true; interval = "*-*-* *:15:00"; - sshKey = pass.secrets."/root/.ssh/id_ed25519".path; + sshKey = gnupg.secrets."/root/.ssh/id_ed25519".path; commonArgs = [ "--no-sync-snap" "--create-bookmark" diff --git a/machines/losurdo/system.nix b/machines/losurdo/system.nix index c1bf4f8..d95923a 100644 --- a/machines/losurdo/system.nix +++ b/machines/losurdo/system.nix @@ -1,7 +1,6 @@ { pkgs, lib, config, ... }: let inherit (config) networking; - inherit (config.security) pass; in { # This value determines the NixOS release with which your system is to be diff --git a/machines/losurdo/transmission.nix b/machines/losurdo/transmission.nix index 57501ce..4a97c8c 100644 --- a/machines/losurdo/transmission.nix +++ b/machines/losurdo/transmission.nix @@ -2,7 +2,7 @@ let inherit (config.services) transmission; inherit (config.users) users; - inherit (config.security) pass; + inherit (config.security) gnupg; in { users.groups.transmission.members = [ @@ -13,14 +13,14 @@ networking.nftables.ruleset = '' add rule inet filter net2fw udp dport ${toString transmission.settings.peer-port} counter accept comment "Transmission" add rule inet filter fw2net meta skuid ${transmission.user} counter accept comment "Transmission" ''; -security.pass.secrets."transmission/settings.json" = {}; +security.gnupg.secrets."transmission/settings.json" = {}; systemd.services.transmission = { - after = [ pass.secrets."transmission/settings.json".service ]; - requires = [ pass.secrets."transmission/settings.json".service ]; + after = [ gnupg.secrets."transmission/settings.json".service ]; + requires = [ gnupg.secrets."transmission/settings.json".service ]; }; services.transmission = { enable = true; - credentialsFile = pass.secrets."transmission/settings.json".path; + credentialsFile = gnupg.secrets."transmission/settings.json".path; settings = { message-level = 3; download-dir = "Downloads"; diff --git a/machines/losurdo/users.nix b/machines/losurdo/users.nix index 7e85cdd..067243c 100644 --- a/machines/losurdo/users.nix +++ b/machines/losurdo/users.nix @@ -41,5 +41,5 @@ users = { }; }; -security.pass.secrets."/root/.ssh/id_ed25519" = {}; +security.gnupg.secrets."/root/.ssh/id_ed25519" = {}; } diff --git a/machines/mermet/croc.nix b/machines/mermet/croc.nix new file mode 100644 index 0000000..2b3f939 --- /dev/null +++ b/machines/mermet/croc.nix @@ -0,0 +1,14 @@ +{ pkgs, lib, config, ... }: +let + inherit (builtins.extraBuiltins) pass-chomp; + croc = config.services.croc; +in +{ +networking.nftables.ruleset = '' + add rule inet filter net2fw tcp dport {${lib.concatMapStringsSep "," toString croc.ports}} counter accept comment "croc" +''; +services.croc = { + enable = true; + pass = pass-chomp "machines/mermet/croc/pass"; +}; +} diff --git a/machines/mermet/knot/autogeree.net.nix b/machines/mermet/knot/autogeree.net.nix index 018c773..4dd2837 100644 --- a/machines/mermet/knot/autogeree.net.nix +++ b/machines/mermet/knot/autogeree.net.nix @@ -6,7 +6,7 @@ let inherit (builtins.extraBuiltins) git; inherit (config) networking; inherit (config.services) knot; - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.users) users groups; # Use the Git commit time of the ${domain}.nix file to set the serial number. # WARNING: the ${domain}.nix must be committed into Git for this to work. @@ -97,16 +97,17 @@ services.knot.zones."${domain}" = { @ CAA 128 issue "letsencrypt.org" ''; }; +users.users.knot.extraGroups = [ groups.keys.name ]; services.knot = { - keyFiles = [ pass.secrets."knot/tsig/${domain}/acme.conf".path ]; + keyFiles = [ gnupg.secrets."knot/tsig/${domain}/acme.conf".path ]; }; -security.pass.secrets."knot/tsig/${domain}/acme.conf" = { +security.gnupg.secrets."knot/tsig/${domain}/acme.conf" = { # Generated with: keymgr -t acme_${domainID} user = users.knot.name; }; systemd.services.knot = { - after = [ pass.secrets."knot/tsig/${domain}/acme.conf".service ]; - wants = [ pass.secrets."knot/tsig/${domain}/acme.conf".service ]; + after = [ gnupg.secrets."knot/tsig/${domain}/acme.conf".service ]; + wants = [ gnupg.secrets."knot/tsig/${domain}/acme.conf".service ]; }; /* Useless since the zone is public services.unbound.extraConfig = '' diff --git a/machines/mermet/knot/sourcephile.fr.nix b/machines/mermet/knot/sourcephile.fr.nix index 43228b4..cd3c7e3 100644 --- a/machines/mermet/knot/sourcephile.fr.nix +++ b/machines/mermet/knot/sourcephile.fr.nix @@ -5,7 +5,7 @@ let inherit (builtins) attrValues; inherit (builtins.extraBuiltins) git; inherit (config) networking; - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.services) knot; inherit (config.users) users groups; # Use the Git commit time of the ${domain}.nix file to set the serial number. @@ -118,16 +118,17 @@ services.knot.zones."${domain}" = { @ CAA 128 issue "letsencrypt.org" ''; }; +users.users.knot.extraGroups = [ groups.keys.name ]; services.knot = { - keyFiles = [ pass.secrets."knot/tsig/${domain}/acme.conf".path ]; + keyFiles = [ gnupg.secrets."knot/tsig/${domain}/acme.conf".path ]; }; -security.pass.secrets."knot/tsig/${domain}/acme.conf" = { +security.gnupg.secrets."knot/tsig/${domain}/acme.conf" = { # Generated with: keymgr -t acme_${domainID} user = users.knot.name; }; systemd.services.knot = { - after = [ pass.secrets."knot/tsig/${domain}/acme.conf".service ]; - wants = [ pass.secrets."knot/tsig/${domain}/acme.conf".service ]; + after = [ gnupg.secrets."knot/tsig/${domain}/acme.conf".service ]; + wants = [ gnupg.secrets."knot/tsig/${domain}/acme.conf".service ]; }; /* Useless since the zone is public services.unbound.extraConfig = '' diff --git a/machines/mermet/rspamd.nix b/machines/mermet/rspamd.nix index 3e1b7ea..362caab 100644 --- a/machines/mermet/rspamd.nix +++ b/machines/mermet/rspamd.nix @@ -3,7 +3,7 @@ let inherit (builtins) attrNames listToAttrs readFile; inherit (lib) types; inherit (pkgs.lib) unlinesAttrs; - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.services) postfix rspamd dovecot2 redis; inherit (config.users) users; in @@ -96,7 +96,7 @@ services.rspamd = { controller = { includes = [ "$CONFDIR/worker-controller.inc" - pass.secrets."rspamd/controller/hashedPassword".path + gnupg.secrets."rspamd/controller/hashedPassword".path ]; bindSockets = [ "127.0.0.1:11334" @@ -108,15 +108,15 @@ services.rspamd = { }; }; }; -security.pass.secrets."rspamd/controller/hashedPassword" = { +security.gnupg.secrets."rspamd/controller/hashedPassword" = { # Generated with: rspamadm pw user = rspamd.user; pipe = ''${pkgs.gnused}/bin/sed -e 's/.*/password = "\0";/' ''; postStart = "systemctl try-restart --no-block rspamd"; # rspamd does not support reloading so far }; systemd.services.rspamd = { - wants = [ pass.secrets."rspamd/controller/hashedPassword".service ]; - after = [ pass.secrets."rspamd/controller/hashedPassword".service ]; + wants = [ gnupg.secrets."rspamd/controller/hashedPassword".service ]; + after = [ gnupg.secrets."rspamd/controller/hashedPassword".service ]; }; /* services.postfix.extraConfig = '' diff --git a/machines/mermet/rspamd/autogeree.net.nix b/machines/mermet/rspamd/autogeree.net.nix index b858654..e5f0b35 100644 --- a/machines/mermet/rspamd/autogeree.net.nix +++ b/machines/mermet/rspamd/autogeree.net.nix @@ -1,7 +1,7 @@ { domain, ... }: { pkgs, lib, config, ... }: let - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.services) rspamd; selector = "20200101"; in @@ -18,12 +18,12 @@ services.knot.zones."${domain}".data = '' "+hH+Mr/4V1wnKtdosk/7+3VIQ6clTIfWhD6PlnWd78Uo5lfWnYxTem7EMc2q7j6tzGwj+Q+b4Li9fdhLqxGuD0V64/nVZit90b0HyfiV5srln2lK6Hczrwqr0gOEBGQ4YeLjOF6ldaV01mFWR9ddr9a5/gVCqw8vw7vhqXvU7yK8VHW2rdsvkNZ0bDOa66MCveD7pH2vyljrfZq9k0T/NLHrsu8CAwEAAQ==" ) ''; -security.pass.secrets."rspamd/dkim/${domain}/${selector}.key" = { +security.gnupg.secrets."rspamd/dkim/${domain}/${selector}.key" = { user = rspamd.user; postStart = "systemctl try-restart --no-block rspamd"; }; systemd.services.rspamd = { - wants = [ pass.secrets."rspamd/dkim/${domain}/${selector}.key".service ]; - after = [ pass.secrets."rspamd/dkim/${domain}/${selector}.key".service ]; + wants = [ gnupg.secrets."rspamd/dkim/${domain}/${selector}.key".service ]; + after = [ gnupg.secrets."rspamd/dkim/${domain}/${selector}.key".service ]; }; } diff --git a/machines/mermet/rspamd/sourcephile.fr.nix b/machines/mermet/rspamd/sourcephile.fr.nix index 6b10375..086c51f 100644 --- a/machines/mermet/rspamd/sourcephile.fr.nix +++ b/machines/mermet/rspamd/sourcephile.fr.nix @@ -1,7 +1,7 @@ { domain, ... }: { pkgs, lib, config, ... }: let - inherit (config.security) pass; + inherit (config.security) gnupg; inherit (config.services) rspamd; selector = "20200101"; in @@ -22,12 +22,12 @@ services.knot.zones."${domain}".data = '' "rWWtSTdO8DilDqN8CAwEAAQ==" ) ''; -security.pass.secrets."rspamd/dkim/${domain}/${selector}.key" = { +security.gnupg.secrets."rspamd/dkim/${domain}/${selector}.key" = { user = rspamd.user; postStart = "systemctl try-restart --no-block rspamd"; }; systemd.services.rspamd = { - after = [ pass.secrets."rspamd/dkim/${domain}/${selector}.key".service ]; - wants = [ pass.secrets."rspamd/dkim/${domain}/${selector}.key".service ]; + after = [ gnupg.secrets."rspamd/dkim/${domain}/${selector}.key".service ]; + wants = [ gnupg.secrets."rspamd/dkim/${domain}/${selector}.key".service ]; }; } diff --git a/machines/mermet/security.nix b/machines/mermet/security.nix index c3c0bac..e74f90e 100644 --- a/machines/mermet/security.nix +++ b/machines/mermet/security.nix @@ -1,33 +1,44 @@ { pkgs, lib, config, machineName, ... }: let - inherit (config.security) pass; + inherit (config.security) gnupg; rootKey = "root/key"; initrdKey = "initrd/ssh.key"; + keygrip = "89F52A879E0019A966503AFFDE72EEA84CDFA3A7"; in { imports = [ ]; -security.pass.store = ../../../sec/pass/machines/losurdo; +security.gnupg.store = builtins.getEnv "PASSWORD_STORE_DIR" + "/machines/${machineName}"; +services.openssh.extraConfig = '' + StreamLocalBindUnlink yes +''; installer.ssh-nixos = { - PATH = with pkgs; [gnupg openssh]; - script = lib.mkBefore '' - # Send the rootKey's passphrase - gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | - ssh '${config.installer.ssh-nixos.target}' \ - install -D -m 400 -o root -g root /dev/stdin /${rootKey}.pass + PATH = [pkgs.gnupg pkgs.openssh]; + script = lib.mkMerge [ + (lib.mkBefore '' + # Send the SSH key of the initrd + gpg --decrypt '${gnupg.store}/${initrdKey}.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} + '') + (lib.mkBefore '' + # Send the rootKey + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts '&&' \ + gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import || true - # Send the rootKey - gpg --decrypt '${pass.store}/${rootKey}.pass.gpg' | - gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | - ssh '${config.installer.ssh-nixos.target}' \ - gpg --batch --pinentry-mode loopback --passphrase-file /root/key.pass --import - - # Send the SSH key of the initrd - gpg --decrypt '${pass.store}/${initrdKey}.gpg' | - ssh '${config.installer.ssh-nixos.target}' \ - install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} - ''; + # Send the rootKey's passphrase + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts '&&' \ + gpg-preset-passphrase \ + --homedir /var/lib/gnupg \ + --preset ${keygrip} || true + '') + ]; }; boot.initrd.network.ssh.hostKeys = [ "/root/${initrdKey}" ]; } diff --git a/nixos/modules/security/pass.nix b/nixos/modules/security/pass.nix deleted file mode 100644 index a2141d5..0000000 --- a/nixos/modules/security/pass.nix +++ /dev/null @@ -1,165 +0,0 @@ -{ pkgs, lib, config, ... }: -let - inherit (builtins) dirOf head listToAttrs match split; - inherit (lib) types; - inherit (config.security) pass; - escapeUnitName = name: - lib.concatMapStrings (s: if lib.isList s then "-" else s) - (split "[^a-zA-Z0-9_.\\-]+" name); -in -{ -options.security.pass = { - store = lib.mkOption { - type = types.path; - description = '' - Default path to the password-store of the orchestrating system. - ''; - # Does not copy the entire password-store into the Nix store, - # only the keys actually used will be. - apply = toString; - }; - secrets = lib.mkOption { - default = {}; - type = types.attrsOf (types.submodule ({name, config, ...}: { - options = { - gpg = lib.mkOption { - type = types.path; - default = builtins.path { - path = pass.store + "/${name}.gpg"; - name = "${escapeUnitName name}.gpg"; - }; - description = '' - The path to the gnupg-encrypted secret. - It will be copied into the Nix store of the orchestrating and of the target system. - It must be decipherable by an OpenPGP key within gnupgHome, - whose passhrase is on the target system into passphraseFile. - Defaults to the name of the secret, prefixed by passwordStore - and suffixed by .gpg. - ''; - }; - gnupgHome = lib.mkOption { - type = types.str; - default = "/root/.gnupg"; - description = '' - The directory on the target system to the gnupg home - used to decrypt the secret. - ''; - }; - passphraseFile = lib.mkOption { - type = types.str; - default = "/root/key.pass"; - description = '' - The directory on the target system to a file containing - the password of an OpenPGP key in gnupgHome, - to which gpg secret is encrypted to. - ''; - }; - mode = lib.mkOption { - type = types.str; - default = "400"; - description = '' - Permission mode of the secret path. - ''; - }; - user = lib.mkOption { - type = types.str; - default = "root"; - description = '' - Owner of the secret path. - ''; - }; - group = lib.mkOption { - type = types.str; - default = "root"; - description = '' - Group of the secret path. - ''; - }; - pipe = lib.mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Shell command taking the deciphered secret on its standard input - and which must put on its standard output - the actual material to be installed. - This allows to decorate the secret with non-secret bits. - ''; - }; - path = lib.mkOption { - type = types.str; - default = name; - apply = p: if match "^/.*" p == null then "/run/pass-secrets/"+p+"/file" else p; - description = '' - The path on the target system where the secret is installed to. - Any non-absolute path is relative to /run/pass-secrets. - Default to the name of the secret. - ''; - }; - service = lib.mkOption { - type = types.str; - default = "secret-" + escapeUnitName name + ".service"; - description = '' - The name of the systemd service. - Useful to put constraints like after or wants - into services requiring this secret. - ''; - }; - postStart = lib.mkOption { - type = types.lines; - default = ""; - example = "systemctl reload nginx.service"; - description = '' - Commands to run after new secrets go live. Typically - the web server and other servers using secrets need to - be reloaded. - ''; - }; - }; - })); - }; -}; -config = lib.mkIf (pass.secrets != {}) { - systemd.services = - lib.mapAttrs' (target: secret: - lib.nameValuePair (lib.removeSuffix ".service" secret.service) { - description = "Install secret ${secret.path}"; - script = '' - set -o pipefail - set -eux - decrypt() { - { - ${pkgs.gnupg}/bin/gpg --batch --pinentry-mode loopback \ - --passphrase-file '${secret.passphraseFile}' \ - --decrypt '${secret.gpg}' \ - ${lib.optionalString (secret.pipe != null) (" | "+secret.pipe)} - } | - install -D -m '${secret.mode}' -o '${secret.user}' -g '${secret.group}' /dev/stdin \ - '${secret.path}' - } - while ! decrypt; do sleep $((1 + ($RANDOM % 10))); done - ''; - postStart = lib.optionalString (secret.postStart != "") '' - set -eux - ${secret.postStart} - ''; - restartIfChanged = true; - restartTriggers = [ - secret.passphraseFile - secret.gpg - ]; - serviceConfig = { - Type = "oneshot"; - Environment = "GNUPGHOME=${secret.gnupgHome}"; - PrivateTmp = true; - RemainAfterExit = true; - WorkingDirectory = dirOf secret.gnupgHome; - } // lib.optionalAttrs (match "^/.*" target == null) { - RuntimeDirectory = lib.removePrefix "/run/" (dirOf secret.path); - RuntimeDirectoryMode = "711"; - RuntimeDirectoryPreserve = false; - }; - } - ) pass.secrets; -}; -meta.maintainers = with lib.maintainers; [ julm ]; -} diff --git a/nixpkgs/patches/security.gnupg.diff b/nixpkgs/patches/security.gnupg.diff new file mode 100644 index 0000000..ad7bf73 --- /dev/null +++ b/nixpkgs/patches/security.gnupg.diff @@ -0,0 +1,270 @@ +diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix +index f361163ca63..8c199f0fc25 100644 +--- a/nixos/modules/module-list.nix ++++ b/nixos/modules/module-list.nix +@@ -193,6 +193,7 @@ + ./security/lock-kernel-modules.nix + ./security/misc.nix + ./security/oath.nix ++ ./security/gnupg.nix + ./security/pam.nix + ./security/pam_usb.nix + ./security/pam_mount.nix +diff --git a/nixos/modules/security/gnupg.nix b/nixos/modules/security/gnupg.nix +new file mode 100644 +index 00000000000..a7a3c752267 +--- /dev/null ++++ b/nixos/modules/security/gnupg.nix +@@ -0,0 +1,252 @@ ++{ config, lib, pkgs, ... }: ++let ++ inherit (builtins) dirOf match split; ++ inherit (lib) types; ++ cfg = config.security.gnupg; ++ gnupgHome = "/var/lib/gnupg"; ++ escapeUnitName = name: ++ lib.concatMapStrings (s: if lib.isList s then "-" else s) ++ (split "[^a-zA-Z0-9_.\\-]+" name); ++in ++{ ++options.security.gnupg = { ++ store = lib.mkOption { ++ type = types.path; ++ default = "/root/.password-store"; ++ description = '' ++ Default base path for the gpg option ++ of the secrets. ++ Note that you may set it up with something like: ++ builtins.getEnv "PASSWORD_STORE_DIR" + "/machines/example". ++ ''; ++ # Does not copy the entire password-store into the Nix store, ++ # only the keys actually used will be. ++ apply = toString; ++ }; ++ secrets = lib.mkOption { ++ description = "Available secrets."; ++ default = {}; ++ example = { ++ "/root/.ssh/id_ed25519" = {}; ++ "knot/tsig/example.org/acme.conf" = { ++ user = "knot"; ++ }; ++ "lego/example.org/rfc2136" = { ++ pipe = " ++ cat - ++ cat /var/lib/gnupg/. ++ Defaults to the name of the secret, ++ prefixed by the path of the store ++ and suffixed by .gpg. ++ ''; ++ }; ++ mode = lib.mkOption { ++ type = types.str; ++ default = "400"; ++ description = '' ++ Permission mode of the secret path. ++ ''; ++ }; ++ user = lib.mkOption { ++ type = types.str; ++ default = "root"; ++ description = '' ++ Owner of the secret path. ++ ''; ++ }; ++ group = lib.mkOption { ++ type = types.str; ++ default = "root"; ++ description = '' ++ Group of the secret path. ++ ''; ++ }; ++ pipe = lib.mkOption { ++ type = types.nullOr types.str; ++ default = null; ++ apply = x: if x == null then null else pkgs.writeShellScript "pipe" x; ++ description = '' ++ Shell script taking the deciphered secret on its standard input ++ and which must put on its standard output ++ the actual secret material to be installed. ++ This allows to decorate the secret with non-secret bits. ++ ''; ++ }; ++ path = lib.mkOption { ++ type = types.str; ++ default = name; ++ apply = p: if match "^/.*" p == null then "/run/keys/gnupg/"+p+"/file" else p; ++ description = '' ++ The path on the target system where the secret is installed to. ++ Default to the name of the secret, ++ prefixed by /run/keys/gnupg/ ++ and suffixed by /file, ++ if non-absolute. ++ ''; ++ }; ++ service = lib.mkOption { ++ type = types.str; ++ default = "secret-" + escapeUnitName name + ".service"; ++ description = '' ++ The name of the systemd service. ++ Useful to put constraints like after or wants ++ into services requiring this secret. ++ ''; ++ }; ++ postStart = lib.mkOption { ++ type = types.lines; ++ default = ""; ++ example = "systemctl reload nginx.service"; ++ description = '' ++ Commands to run after new secrets go live. ++ Typically the web server and other servers using secrets ++ need to be reloaded. ++ ''; ++ }; ++ postStop = lib.mkOption { ++ type = types.lines; ++ default = ""; ++ example = ''shred -u "$secret_file"''; ++ description = '' ++ Commands to run after stopping the service. ++ Typically removing a persistent secret. ++ For convenience the path of the secret ++ is provided in the shell variable secret_file. ++ ''; ++ }; ++ }; ++ })); ++ }; ++ enableGpgAgent = lib.mkEnableOption ''gpg-agent for decrypting secrets. ++ ++ Otherwise, you'll have to forward an agent-extra-socket: ++ ++ $ ssh -nNT root@example.org -o StreamLocalBindUnlink=yes -R /var/lib/gnupg/S.gpg-agent:$(gpgconf --list-dirs agent-extra-socket) ++ ++ '' // {default = true; example = false;}; ++ gpgAgentFlags = lib.mkOption { ++ type = types.listOf types.str; ++ default = [ ++ "--default-cache-ttl" "600" ++ "--max-cache-ttl" "7200" ++ ]; ++ description = '' ++ Extra flags passed to the gpg-agent ++ used to decrypt secrets. ++ ''; ++ }; ++}; ++config = lib.mkIf (cfg.secrets != {}) { ++ systemd.sockets."gpg-agent" = lib.optionalAttrs cfg.enableGpgAgent { ++ description = "Socket for gpg-agent"; ++ wantedBy = ["sockets.target"]; ++ socketConfig.ListenStream = "${gnupgHome}/S.gpg-agent"; ++ socketConfig.SocketMode = "0600"; ++ }; ++ environment.systemPackages = ++ # TODO: maybe this would be better to do that directly in pkgs.gnupg? ++ let gpgPresetPassphrase = pkgs.runCommand "gpg-preset-passphrase" ++ { preferLocalBuild = true; ++ allowSubstitutes = false; ++ } '' ++ mkdir -p $out/bin ++ ln -s -t $out/bin ${pkgs.gnupg}/libexec/gpg-preset-passphrase ++ ''; in ++ [ pkgs.gnupg gpgPresetPassphrase ]; ++ systemd.services = ++ lib.optionalAttrs cfg.enableGpgAgent { ++ gpg-agent = { ++ description = "gpg-agent for decrypting GnuPG-protected secrets"; ++ requires = ["gpg-agent.socket"]; ++ serviceConfig = { ++ Type = "simple"; ++ # Because /run/user/0 is wiped out by pam_systemd when root logouts, ++ # systemd.services.gpg-agent cannot put its socket in ++ # the path expected by gpg: ++ # /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts ++ # derived from ${gnupgHome}. ++ # ++ # But GPG_AGENT_INFO is ignored with gpg >= 2.1, ++ # hence this hack makes gpg connect to ${gnupgHome}/S.gpg-agent ++ # by relaxing permissions of the expected socket directory. ++ # With this hack, uploading the passphrase should now be doable with just: ++ # ssh root@example.org 'gpg-preset-passphrase --homedir /var/lib/gnupg --preset $keygrip' ++ ExecStartPre = "${pkgs.coreutils}/bin/install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts"; ++ ExecStart = ''${pkgs.gnupg}/bin/gpg-agent \ ++ --supervised \ ++ --homedir '${gnupgHome}' \ ++ --allow-loopback-pinentry \ ++ --allow-preset-passphrase \ ++ ${lib.escapeShellArgs cfg.gpgAgentFlags} ++ ''; ++ Restart = "on-failure"; ++ RestartSec = 5; ++ StateDirectory = ["gnupg"]; ++ StateDirectoryMode = "700"; ++ }; ++ }; ++ } // ++ lib.mapAttrs' (target: secret: ++ lib.nameValuePair (lib.removeSuffix ".service" secret.service) { ++ description = "Install secret ${secret.path}"; ++ after = ["gpg-agent.service"]; ++ wants = ["gpg-agent.service"]; ++ script = '' ++ set -o pipefail ++ set -eux ++ decrypt() { ++ # In case /run/user/0 has been cleaned up by pam_systemd ++ install -D -m 640 -d /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts ++ ${pkgs.gnupg}/bin/gpg --homedir '${gnupgHome}' --no-autostart --batch --decrypt '${secret.gpg}' | ++ ${lib.optionalString (secret.pipe != null) (secret.pipe+" |")} \ ++ install -D -m '${secret.mode}' -o '${secret.user}' -g '${secret.group}' /dev/stdin '${secret.path}' ++ } ++ while ! decrypt; do sleep $((1 + ($RANDOM % 12))); done ++ ''; ++ postStart = lib.optionalString (secret.postStart != "") '' ++ set -eux ++ ${secret.postStart} ++ ''; ++ postStop = lib.optionalString (secret.postStop != "") '' ++ secret_file='${secret.path}' ++ set -eux ++ ${secret.postStop} ++ ''; ++ serviceConfig = { ++ Type = "oneshot"; ++ PrivateTmp = true; ++ RemainAfterExit = true; ++ } // lib.optionalAttrs (match "^/.*" target == null) { ++ RuntimeDirectory = lib.removePrefix "/run/" (dirOf secret.path); ++ RuntimeDirectoryMode = "711"; ++ RuntimeDirectoryPreserve = false; ++ }; ++ } ++ ) cfg.secrets; ++}; ++meta.maintainers = with lib.maintainers; [ julm ]; ++} diff --git a/nixpkgs/patches/security.pass.diff b/nixpkgs/patches/security.pass.diff index 220df8c..df45009 100644 --- a/nixpkgs/patches/security.pass.diff +++ b/nixpkgs/patches/security.pass.diff @@ -12,15 +12,15 @@ index f361163ca63..5e306de7dad 100644 ./security/pam_mount.nix diff --git a/nixos/modules/security/pass.nix b/nixos/modules/security/pass.nix new file mode 100644 -index 00000000000..bf72d06e1ea +index 00000000000..28a0af36ac5 --- /dev/null +++ b/nixos/modules/security/pass.nix -@@ -0,0 +1,179 @@ -+{ pkgs, lib, config, ... }: +@@ -0,0 +1,216 @@ ++{ config, lib, pkgs, ... }: +let + inherit (builtins) dirOf head listToAttrs match split; + inherit (lib) types; -+ inherit (config.security) pass; ++ cfg = config.security.pass; + escapeUnitName = name: + lib.concatMapStrings (s: if lib.isList s then "-" else s) + (split "[^a-zA-Z0-9_.\\-]+" name); @@ -29,6 +29,7 @@ index 00000000000..bf72d06e1ea +options.security.pass = { + store = lib.mkOption { + type = types.path; ++ default = lib.maybeEnv "PASSWORD_STORE_DIR" ".password-store"; + description = '' + Default path to the password-store of the orchestrating system. + ''; @@ -36,14 +37,57 @@ index 00000000000..bf72d06e1ea + # only the keys actually used will be. + apply = toString; + }; ++ passphraseFile = lib.mkOption { ++ type = types.str; ++ default = "/root/key.pass"; ++ description = '' ++ The directory on the target system to a file containing ++ the password of an OpenPGP key in gnupgHome, ++ to which gpg secret is encrypted to. ++ Set it to a temporary directory like /run/keys/key.pass ++ if you don't want it to persist accross reboot. ++ It can be customized per secret. ++ ''; ++ }; ++ gnupgHome = lib.mkOption { ++ type = types.str; ++ default = "/root/.gnupg"; ++ description = '' ++ The directory on the target system to the gnupg home ++ used to decrypt the secret. ++ It can be customized per secret. ++ ''; ++ }; + secrets = lib.mkOption { ++ description = "Available secrets."; + default = {}; ++ example = { ++ "/root/.ssh/id_ed25519" = {}; ++ "knot/tsig/example.org/acme.conf" = { ++ user = "knot"; ++ }; ++ "lego/example.org/rfc2136" = { ++ pipe = " ++ cat - ++ cat gnupg home -+ used to decrypt the secret. ++ Custom gnupgHome for this secret. + ''; + }; + passphraseFile = lib.mkOption { + type = types.str; -+ default = "/root/key.pass"; ++ default = cfg.passphraseFile; + description = '' -+ The directory on the target system to a file containing -+ the password of an OpenPGP key in gnupgHome, -+ to which gpg secret is encrypted to. ++ Custom passphraseFile for this secret. + ''; + }; + mode = lib.mkOption { @@ -96,10 +137,11 @@ index 00000000000..bf72d06e1ea + pipe = lib.mkOption { + type = types.nullOr types.str; + default = null; ++ apply = x: if x == null then null else pkgs.writeShellScript "pipe" x; + description = '' -+ Shell command taking the deciphered secret on its standard input ++ Shell script taking the deciphered secret on its standard input + and which must put on its standard output -+ the actual material to be installed. ++ the actual secret material to be installed. + This allows to decorate the secret with non-secret bits. + ''; + }; @@ -139,13 +181,15 @@ index 00000000000..bf72d06e1ea + description = '' + Commands to run after stopping the service. + Typically removing a persistent secret. ++ For convenience the path of the secret ++ is provided in the shell variable secret_file. + ''; + }; + }; + })); + }; +}; -+config = lib.mkIf (pass.secrets != {}) { ++config = lib.mkIf (cfg.secrets != {}) { + systemd.services = + lib.mapAttrs' (target: secret: + lib.nameValuePair (lib.removeSuffix ".service" secret.service) { @@ -154,12 +198,10 @@ index 00000000000..bf72d06e1ea + set -o pipefail + set -eux + decrypt() { -+ { + ${pkgs.gnupg}/bin/gpg --batch --pinentry-mode loopback \ + --passphrase-file '${secret.passphraseFile}' \ + --decrypt '${secret.gpg}' \ -+ ${lib.optionalString (secret.pipe != null) (" | "+secret.pipe)} -+ } | ++ ${lib.optionalString (secret.pipe != null) (" | "+secret.pipe)} | + install -D -m '${secret.mode}' -o '${secret.user}' -g '${secret.group}' /dev/stdin \ + '${secret.path}' + } @@ -174,11 +216,6 @@ index 00000000000..bf72d06e1ea + set -eux + ${secret.postStop} + ''; -+ restartIfChanged = true; -+ restartTriggers = [ -+ secret.passphraseFile -+ secret.gpg -+ ]; + serviceConfig = { + Type = "oneshot"; + Environment = "GNUPGHOME=${secret.gnupgHome}"; @@ -191,7 +228,7 @@ index 00000000000..bf72d06e1ea + RuntimeDirectoryPreserve = false; + }; + } -+ ) pass.secrets; ++ ) cfg.secrets; +}; +meta.maintainers = with lib.maintainers; [ julm ]; +} diff --git a/nixpkgs/patches/services.croc.diff b/nixpkgs/patches/services.croc.diff new file mode 100644 index 0000000..c8678bb --- /dev/null +++ b/nixpkgs/patches/services.croc.diff @@ -0,0 +1,96 @@ +diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix +index f361163ca63..24dcf53e635 100644 +--- a/nixos/modules/module-list.nix ++++ b/nixos/modules/module-list.nix +@@ -598,6 +598,7 @@ + ./services/networking/coredns.nix + ./services/networking/corerad.nix + ./services/networking/coturn.nix ++ ./services/networking/croc.nix + ./services/networking/dante.nix + ./services/networking/ddclient.nix + ./services/networking/dhcpcd.nix +diff --git a/nixos/modules/services/networking/croc.nix b/nixos/modules/services/networking/croc.nix +new file mode 100644 +index 00000000000..adba6f7f565 +--- /dev/null ++++ b/nixos/modules/services/networking/croc.nix +@@ -0,0 +1,78 @@ ++{ config, lib, pkgs, ... }: ++let ++ inherit (lib) types; ++ cfg = config.services.croc; ++in ++{ ++ options.services.croc = { ++ enable = lib.mkEnableOption "croc relay"; ++ ports = lib.mkOption { ++ type = types.listOf types.port; ++ default = [9009 9010 9011 9012 9013]; ++ description = "Ports of the relay."; ++ }; ++ pass = lib.mkOption { ++ type = types.str; ++ default = "pass123"; ++ description = "Password for the relay (warning: it will be visible in the Nix store and the list of processes)."; ++ }; ++ }; ++ ++ config = lib.mkIf cfg.enable { ++ systemd.services.croc = { ++ after = [ "network.target" ]; ++ wantedBy = [ "multi-user.target" ]; ++ serviceConfig = { ++ ExecStart = "${pkgs.croc}/bin/croc --pass '${cfg.pass}' relay --ports ${lib.concatMapStringsSep "," toString cfg.ports}"; ++ # systemd-analyze security croc ++ # → Overall exposure level for croc.service: 1.1 OK ++ AmbientCapabilities = ""; ++ CapabilityBoundingSet = ""; ++ DynamicUser = true; ++ LockPersonality = true; ++ MemoryDenyWriteExecute = true; ++ NoNewPrivileges = true; ++ PrivateDevices = true; ++ PrivateMounts = true; ++ PrivateNetwork = false; ++ PrivateTmp = true; ++ PrivateUsers = true; ++ ProtectClock = true; ++ ProtectControlGroups = true; ++ ProtectHome = true; ++ ProtectHostname = true; ++ ProtectKernelLogs = true; ++ ProtectKernelModules = true; ++ ProtectKernelTunables = true; ++ ProtectSystem = "strict"; ++ RemoveIPC = true; ++ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; ++ RestrictNamespaces = true; ++ RestrictRealtime = true; ++ RestrictSUIDSGID = true; ++ SystemCallFilter = [ ++ # perf stat -o /dev/stdout -e 'syscalls:sys_enter_*' croc relay | ++ # awk '$1 && $2 ~ /syscalls:/ { sub("syscalls:sys_enter_", ""); print $2 }' | ++ # sort >croc.syscalls ++ # ++ # pkill croc ++ # systemd-analyze syscall-filter | grep -v -e '#' | ++ # sed -e ':loop; /^[^ ]/N; s/\n //; t loop' | ++ # grep --color $(printf ' -e \\<%s\\>' $(cat croc.syscalls)) ++ "@default" "@basic-io" "@file-system" "@io-event" "@ipc" "@network-io" "@process" "@signal" ++ "brk getrandom ioctl madvise mprotect sched_getaffinity sched_yield uname" ++ ]; ++ SystemCallArchitectures = "native"; ++ SystemCallErrorNumber = "EPERM"; ++ UMask = "0077"; ++ }; ++ }; ++ ++ networking.firewall = ++ { allowedTCPPorts = [ cfg.ports ]; ++ allowedUDPPorts = [ cfg.ports ]; ++ }; ++ }; ++ ++ meta.maintainers = with lib.maintainers; [ julm ]; ++} diff --git a/nixpkgs/patches/ssh-nixos.diff b/nixpkgs/patches/ssh-nixos.diff deleted file mode 100644 index 059c3a2..0000000 --- a/nixpkgs/patches/ssh-nixos.diff +++ /dev/null @@ -1,131 +0,0 @@ -diff --git a/nixos/modules/installer/ssh-nixos.nix b/nixos/modules/installer/ssh-nixos.nix -new file mode 100644 -index 00000000000..2822c8814c0 ---- /dev/null -+++ b/nixos/modules/installer/ssh-nixos.nix -@@ -0,0 +1,113 @@ -+{ pkgs, lib, config, ... }: -+let -+ inherit (lib) types; -+ inherit (config) networking; -+ cfg = config.installer.ssh-nixos; -+ nixRunDefaultCommand = "bash"; -+ ssh = pkgs.writeShellScriptBin "ssh" '' -+ set -eu -+ PATH=$OLDPATH -+ set -x -+ ssh -l '${cfg.login}' \ -+ ${lib.escapeShellArgs cfg.sshFlags} ''${SSH_FLAGS:-} "$@" -+ ''; -+in -+{ -+options.installer.ssh-nixos = { -+ PATH = lib.mkOption { -+ type = types.listOf types.package; -+ default = []; -+ apply = lib.makeBinPath; -+ description = "Packages to be appended to the PATH of the script."; -+ }; -+ script = lib.mkOption { -+ type = types.lines; -+ default = ""; -+ example = '' -+ lib.mkBefore '''''' -+ gpg --decrypt initrd/ssh.key.gpg | -+ ssh root@''${config.installer.ssh-nixos.target} \ -+ install -D -m 400 -o root -g root /dev/stdin /root/initrd/ssh.key -+ ''''''; -+ ''; -+ description = '' -+ Install script copying the configured NixOS via SSH -+ to the target -+ and switching to the new configuration. -+ It is made available here for prepending or appending commands -+ with the usual mkBefore and mkAfter. -+ In case you run it often or add multiple ssh calls to it, -+ consider configuring the OpenSSH client with ControlMaster auto -+ to keep the SSH connexion alive between calls to literal. -+ -+ This script is usually run with: -+ -+ $ nix run system.config.installer.ssh-nixos -f nixos.nix -+ -+ where nixos.nix can be: -+ -+ import { -+ system = "x86_64-linux"; -+ configuration = { config, lib, pkgs }: { -+ # Your usual configuration.nix content can go here -+ }; -+ } -+ -+ ''; -+ apply = script: pkgs.writeShellScriptBin nixRunDefaultCommand '' -+ set -eu -+ set -o pipefail -+ export OLDPATH=$PATH:${cfg.PATH} -+ PATH="${ssh}/bin:$OLDPATH" -+ set -x -+ ${script} -+ ''; -+ }; -+ login = lib.mkOption { -+ type = types.str; -+ default = "root"; -+ example = "admin"; -+ description = "Login name passed to ssh."; -+ }; -+ target = lib.mkOption { -+ type = types.str; -+ default = "${networking.hostName}.${networking.domain}"; -+ example = "192.168.1.10"; -+ description = "Destination where to install NixOS passed to ssh."; -+ }; -+ sshFlags = lib.mkOption { -+ type = types.listOf types.str; -+ default = ["-o" "ControlMaster=auto"]; -+ description = '' -+ Extra flags passed to ssh. -+ Environment variable SSH_FLAGS can also be used at runtime. -+ ''; -+ }; -+ nixCopyFlags = lib.mkOption { -+ type = types.listOf types.str; -+ default = ["--substitute-on-destination"]; -+ description = '' -+ Extra flags passed to nix copy. -+ Environment variable NIX_COPY_FLAGS can also be used at runtime. -+ ''; -+ }; -+ profile = lib.mkOption { -+ type = types.str; -+ default = "/nix/var/nix/profiles/system"; -+ }; -+}; -+config = { -+ installer.ssh-nixos.PATH = with pkgs; [nix openssh]; -+ installer.ssh-nixos.script = -+ let nixos = config.system.build.toplevel; in '' -+ nix ''${NIX_FLAGS:-} copy \ -+ --to ssh://'${cfg.target}' \ -+ ${lib.escapeShellArgs cfg.nixCopyFlags} ''${NIX_COPY_FLAGS:-} \ -+ ${nixos} -+ ssh '${cfg.target}' \ -+ nix-env --profile '${cfg.profile}' --set '${nixos}' '&&' \ -+ '${cfg.profile}'/bin/switch-to-configuration "''${NIXOS_SWITCH:-switch}" -+ ''; -+}; -+meta.maintainers = [ lib.maintainers.julm ]; -+} -diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix -index f361163ca63..15659fde11b 100644 ---- a/nixos/modules/module-list.nix -+++ b/nixos/modules/module-list.nix -@@ -80,6 +80,7 @@ - ./i18n/input-method/ibus.nix - ./i18n/input-method/nabi.nix - ./i18n/input-method/uim.nix -+ ./installer/ssh-nixos.nix - ./installer/tools/tools.nix - ./misc/assertions.nix - ./misc/crashdump.nix diff --git a/shell.nix b/shell.nix index 3827a18..d7e5386 100644 --- a/shell.nix +++ b/shell.nix @@ -37,7 +37,7 @@ let localNixpkgsPatches = [ nixpkgs/patches/transmission+apparmor.diff nixpkgs/patches/installer.ssh-nixos.diff - nixpkgs/patches/security.pass.diff + nixpkgs/patches/security.gnupg.diff nixpkgs/patches/services.croc.diff ]; # Build nixpkgs with some patches. @@ -79,6 +79,14 @@ let # julm@sourcephile.fr trusted-key 0xB2450D97085B7B8C ''; + gpgAgentExtraConf = '' + #pretend-request-origin remote + #extra-socket ${toString ./.}/S.gpg-agent.extra + #log-file ${toString ./.}/gpg-agent.log + #no-grab + #debug-level expert + #allow-loopback-pinentry + ''; }; openssl = { enable = true; diff --git a/shell/modules/tools/security/gnupg.nix b/shell/modules/tools/security/gnupg.nix index f096017..3e501eb 100644 --- a/shell/modules/tools/security/gnupg.nix +++ b/shell/modules/tools/security/gnupg.nix @@ -29,7 +29,7 @@ let ${head1} fpr=$(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${uid}" | head1) caps=$(${gpg-with-home}/bin/gpg-with-home \ - --with-colons --fixed-list-mode --with-fingerprint \ + --with-colons --with-fingerprint \ --list-secret-keys -- "=${uid}" | ${pkgs.gnugrep}/bin/grep '^ssb:' | ${pkgs.coreutils}/bin/cut -d : -f 12 || true) @@ -219,7 +219,7 @@ let gpg-fingerprint = pkgs.writeScriptBin "gpg-fingerprint" '' set -eu ${gpg-with-home}/bin/gpg-with-home \ - --with-colons --fixed-list-mode --with-fingerprint --with-subkey-fingerprint \ + --with-colons --with-fingerprint --with-subkey-fingerprint \ --list-public-keys "$@" | while IFS=: read -r t x x x key x x x x uid x do case $t in @@ -235,23 +235,16 @@ let gpg-keygrip = pkgs.writeScriptBin "gpg-keygrip" '' set -eu ${gpg-with-home}/bin/gpg-with-home \ - --with-colons --fixed-list-mode --with-keygrip \ + --with-colons --with-keygrip \ --list-public-keys "$@" | - while IFS=: read -r t x x x key x x x x uid x - do case $t in - (pub|sub|sec|ssb) - while IFS=: read -r t x x x x x x x x grp x - do case $t in (grp) printf '%s\n' "$grp"; break;; - esac done - ;; - esac done + while IFS=: read -r t x x x key x x x x uid x do case $t in (pub|sub|sec|ssb) while IFS=: read -r t x x x x x x x x grp x do case $t in (grp) printf '%s\n' "$grp"; break;; esac done ;; esac done ''; # A wrapper around gpg to get uids. gpg-uid = pkgs.writeScriptBin "gpg-uid" '' set -eu ${gpg-with-home}/bin/gpg-with-home \ - --with-colons --fixed-list-mode \ + --with-colons \ --list-public-keys "$@" | while IFS=: read -r t st x x x x x id x uid x do case $t in @@ -415,14 +408,14 @@ options.gnupg = { }; gpgAgentConf = lib.mkOption { type = types.lines; - apply = s: pkgs.writeText "gpg-agent.conf" s; + apply = s: pkgs.writeText "gpg-agent.conf" (s+"\n"+gnupg.gpgAgentExtraConf); default = let pinentry = pkgs.writeShellScript "pinentry" '' #!${pkgs.runtimeShell} # choose pinentry depending on PINENTRY_USER_DATA # this *only works* with gpg2 # see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=802020 - case "''${PINENTRY_USER_DATA:-tty}" in + case "''${PINENTRY_USER_DATA:-curses}" in curses) exec ${pkgs.pinentry.curses}/bin/pinentry-curses "$@";; #emacs) exec ''${pkgs.pinentry.emacs}/bin/pinentry-emacs "$@";; #gnome3) exec ''${pkgs.pinentry.gnome3}/bin/pinentry-gnome3 "$@";; @@ -455,7 +448,6 @@ options.gnupg = { cert-digest-algo SHA512 charset utf-8 default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 TWOFISH BZIP2 ZLIB ZIP Uncompressed - fixed-list-mode keyid-format 0xlong keyserver-options no-honor-keyserver-url no-auto-key-locate @@ -484,6 +476,13 @@ options.gnupg = { GnuPG's gpg.conf extra content. ''; }; + gpgAgentExtraConf = lib.mkOption { + type = types.lines; + default = ""; + description = '' + GnuPG's gpg-agent.conf extra content. + ''; + }; }; config = lib.mkIf gnupg.enable { nix-shell.buildInputs = [ -- 2.49.0 From d15d9627b3dd9d3c6a4ed8711064a9431c7d6ecd Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Fri, 31 Jul 2020 02:55:13 +0200 Subject: [PATCH 07/16] nix: commit changes during work on services.transmission --- machines/losurdo/hardware.nix | 8 +- machines/losurdo/networking.nix | 3 +- machines/losurdo/nginx.nix | 1 + machines/losurdo/security.nix | 24 +- machines/losurdo/system.nix | 2 + machines/losurdo/transmission.nix | 19 +- machines/mermet/hardware.nix | 8 +- machines/mermet/security.nix | 25 +- nixos/defaults.nix | 13 +- nixos/modules.nix | 2 + .../modules/services/torrent/transmission.nix | 343 --------------- nixos/profiles/hardware/apu2e4.nix | 7 - nixos/profiles/hardware/dl10j.nix | 7 - nixos/profiles/systems/zramSwap.nix | 27 ++ nixpkgs/patches/security.gnupg.diff | 55 ++- nixpkgs/patches/transmission+apparmor.diff | 403 ++++++++++++------ 16 files changed, 399 insertions(+), 548 deletions(-) delete mode 100644 nixos/modules/services/torrent/transmission.nix create mode 100644 nixos/profiles/systems/zramSwap.nix diff --git a/machines/losurdo/hardware.nix b/machines/losurdo/hardware.nix index 5c8fc73..0857044 100644 --- a/machines/losurdo/hardware.nix +++ b/machines/losurdo/hardware.nix @@ -2,6 +2,7 @@ { imports = [ ../../nixos/profiles/hardware/dl10j.nix + ../../nixos/profiles/systems/zramSwap.nix ]; # The 32-bit host id of the machine, formatted as 8 hexadecimal characters. @@ -34,11 +35,4 @@ swapDevices = [ }; } ]; - -zramSwap = { - enable = true; - algorithm = "zstd"; - memoryPercent = 50; - swapDevices = 1; -}; } diff --git a/machines/losurdo/networking.nix b/machines/losurdo/networking.nix index e701012..cb8ee20 100644 --- a/machines/losurdo/networking.nix +++ b/machines/losurdo/networking.nix @@ -106,7 +106,8 @@ networking = { #nameservers = [ ]; nftables.ruleset = '' add rule inet filter input iifname "enp5s0" goto net2fw - add rule inet filter output oifname "enp5s0" goto fw2net + add rule inet filter output oifname "enp5s0" jump fw2net + add rule inet filter output oifname "enp5s0" log level warn prefix \"fw2net: \" counter drop add rule inet filter fw2net ip daddr ${lanNet} counter accept comment "LAN" add rule inet filter fw2net ip daddr 224.0.0.0/4 udp dport 1900 counter accept comment "UPnP" ''; diff --git a/machines/losurdo/nginx.nix b/machines/losurdo/nginx.nix index 06fbc37..d49b434 100644 --- a/machines/losurdo/nginx.nix +++ b/machines/losurdo/nginx.nix @@ -9,6 +9,7 @@ imports = [ nginx/sourcephile.fr.nix ]; users.groups."acme".members = [nginx.user]; +users.groups."transmission".members = [nginx.user]; networking.nftables.ruleset = '' add rule inet filter net2fw tcp dport 8443 counter accept comment "HTTPS" ''; diff --git a/machines/losurdo/security.nix b/machines/losurdo/security.nix index ad6c175..6666dd7 100644 --- a/machines/losurdo/security.nix +++ b/machines/losurdo/security.nix @@ -27,19 +27,21 @@ installer.ssh-nixos = { install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} '') (lib.mkBefore '' - gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | - gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | ssh '${config.installer.ssh-nixos.target}' \ - install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts '&&' \ - gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import || true + gpg-connect-agent --no-autostart --homedir /var/lib/gnupg "'keyinfo --list'" /bye 2>&1 | + grep -qx -e "gpg-connect-agent: no gpg-agent running in this session" \ + -e "S KEYINFO ${keygrip} . . . 1 .*" || { + # Send the rootKey + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | + ssh '${config.installer.ssh-nixos.target}' \ + gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import - # Send the rootKey's passphrase - gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | - ssh '${config.installer.ssh-nixos.target}' \ - install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts '&&' \ - gpg-preset-passphrase \ - --homedir /var/lib/gnupg \ - --preset ${keygrip} || true + # Send the rootKey's passphrase + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + gpg-preset-passphrase --homedir /var/lib/gnupg --preset ${keygrip} + } '') ]; /* diff --git a/machines/losurdo/system.nix b/machines/losurdo/system.nix index d95923a..c55f513 100644 --- a/machines/losurdo/system.nix +++ b/machines/losurdo/system.nix @@ -35,5 +35,7 @@ environment.systemPackages = with pkgs; [ #iptables-nftables-compat gnupg miniupnpc + audit + python ]; } diff --git a/machines/losurdo/transmission.nix b/machines/losurdo/transmission.nix index 4a97c8c..ca92a4f 100644 --- a/machines/losurdo/transmission.nix +++ b/machines/losurdo/transmission.nix @@ -13,21 +13,31 @@ networking.nftables.ruleset = '' add rule inet filter net2fw udp dport ${toString transmission.settings.peer-port} counter accept comment "Transmission" add rule inet filter fw2net meta skuid ${transmission.user} counter accept comment "Transmission" ''; -security.gnupg.secrets."transmission/settings.json" = {}; +#users.groups.keys.members = [ transmission.user ]; +security.gnupg.secrets."transmission/settings.json" = { + user = transmission.user; +}; systemd.services.transmission = { after = [ gnupg.secrets."transmission/settings.json".service ]; requires = [ gnupg.secrets."transmission/settings.json".service ]; }; services.transmission = { enable = true; + performanceNetParameters = true; credentialsFile = gnupg.secrets."transmission/settings.json".path; settings = { message-level = 3; - download-dir = "Downloads"; - incomplete-dir-enabled = false; + download-dir = "/home/julm/dl/torrents"; + incomplete-dir = "/home/julm/dl/torrents/.incoming"; + incomplete-dir-enabled = true; trash-original-torrent-files = false; preallocation = 0; - umask = 63; + umask = 7; # 007 octal, in decimal! + download-queue-enabled = true; + download-queue-size = 5; + peer-id-ttl-hours = 6; + peer-limit-global = 1000; + peer-limit-per-torrent = 100; peer-port = 6882; peer-port-random-on-start = false; @@ -40,7 +50,6 @@ services.transmission = { peer-socket-tos = "lowcost"; queue-stalled-enabled = true; queue-stalled-minutes = 30; - speed-limit-up = 500; speed-limit-up-enabled = true; alt-speed-enabled = true; diff --git a/machines/mermet/hardware.nix b/machines/mermet/hardware.nix index f8b0f4f..c16a204 100644 --- a/machines/mermet/hardware.nix +++ b/machines/mermet/hardware.nix @@ -2,6 +2,7 @@ { imports = [ ../../nixos/profiles/hardware/apu2e4.nix + ../../nixos/profiles/systems/zramSwap.nix ]; # The 32-bit host id of the machine, formatted as 8 hexadecimal characters. @@ -32,11 +33,4 @@ swapDevices = [ }; } ]; - -zramSwap = { - enable = true; - algorithm = "zstd"; - memoryPercent = 50; - swapDevices = 1; -}; } diff --git a/machines/mermet/security.nix b/machines/mermet/security.nix index e74f90e..024b764 100644 --- a/machines/mermet/security.nix +++ b/machines/mermet/security.nix @@ -23,20 +23,21 @@ installer.ssh-nixos = { install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} '') (lib.mkBefore '' - # Send the rootKey - gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | - gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | ssh '${config.installer.ssh-nixos.target}' \ - install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts '&&' \ - gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import || true + "gpg-connect-agent --no-autostart --homedir /var/lib/gnupg 'keyinfo --list' /bye 2>&1" | + grep -qx -e "gpg-connect-agent: no gpg-agent running in this session" \ + -e "S KEYINFO ${keygrip} . . . 1 .*" || { + # Send the rootKey + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | + ssh '${config.installer.ssh-nixos.target}' \ + gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import - # Send the rootKey's passphrase - gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | - ssh '${config.installer.ssh-nixos.target}' \ - install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts '&&' \ - gpg-preset-passphrase \ - --homedir /var/lib/gnupg \ - --preset ${keygrip} || true + # Send the rootKey's passphrase + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + gpg-preset-passphrase --homedir /var/lib/gnupg --preset ${keygrip} + } '') ]; }; diff --git a/nixos/defaults.nix b/nixos/defaults.nix index 1b7549b..0198178 100644 --- a/nixos/defaults.nix +++ b/nixos/defaults.nix @@ -92,7 +92,10 @@ services = { }; journald = { extraConfig = '' - SystemMaxUse=50M + Compress=true + MaxRetentionSec=3month + Storage=persistent + SystemMaxUse=500M ''; }; }; @@ -103,16 +106,19 @@ environment = { systemPackages = with pkgs; [ pkgs.path # WARNING: this is a hack to register the path to Nixpkgs. See nix.nixPath. binutils + conntrack-tools #dnsutils dstat gnupg htop inetutils iotop + linuxPackages.cpupower lsof mailutils multitail ncdu + nmon pv swaplist tcpdump @@ -120,10 +126,11 @@ environment = { tree vim which - linuxPackages.cpupower ]; etc."inputrc".text = lib.readFile defaults/readline/inputrc; + + variables.SYSTEMD_LESS = "FKMRX"; }; programs = { @@ -153,7 +160,9 @@ programs = { mem = "ps -e -orss=,user=,args= | sort -b -k1,1n"; s="sudo systemctl"; + st="sudo systemctl status"; s-u="systemctl --user"; + j="sudo journalctl -u"; nixos-clean="sudo nix-collect-garbage -d"; nixos-history="sudo nix-env --list-generations --profile /nix/var/nix/profiles/system"; diff --git a/nixos/modules.nix b/nixos/modules.nix index 089698c..89825ee 100644 --- a/nixos/modules.nix +++ b/nixos/modules.nix @@ -5,10 +5,12 @@ imports = [ modules/services/databases/openldap.nix modules/services/mail/public-inbox.nix + #/home/julm/src/nix/nixpkgs/nixos/modules/services/torrent/transmission.nix #modules/services/mail/mlmmj.nix ]; disabledModules = [ "services/mail/mlmmj.nix" "services/mail/public-inbox.nix" + #"services/torrent/transmission.nix" ]; } diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix deleted file mode 100644 index 195bfcd..0000000 --- a/nixos/modules/services/torrent/transmission.nix +++ /dev/null @@ -1,343 +0,0 @@ -{ config, lib, pkgs, options, ... }: - -with lib; - -let - cfg = config.services.transmission; - inherit (config.environment) etc; - apparmor = config.security.apparmor.enable; - stateDir = "/var/lib/transmission"; - # TODO: switch to configGen.json once RFC0042 is implemented - settingsFile = pkgs.writeText "settings.json" (builtins.toJSON (cfg.settings // { - download-dir = "${stateDir}/Downloads"; - incomplete-dir = "${stateDir}/.incomplete"; - })); - settingsDir = ".config/transmission-daemon"; - makeAbsolute = base: path: - if builtins.match "^/.*" path == null - then base+"/"+path else path; -in -{ - options = { - services.transmission = { - enable = mkEnableOption '' - Whether or not to enable the headless Transmission BitTorrent daemon. - - Transmission daemon can be controlled via the RPC interface using - transmission-remote, the WebUI (http://${cfg.settings.rpc-bind-address}:${toString cfg.settings.rpc-port}/ by default), - or other clients like stig or tremc. - - Torrents are downloaded to ${cfg.settings.download-dir} by default and are - accessible to users in the "transmission" group. - ''; - - settings = mkOption rec { - # TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented - type = types.attrs; - apply = attrs: - let super = recursiveUpdate default attrs; in - super // { - download-dir = makeAbsolute cfg.home super.download-dir; - incomplete-dir = makeAbsolute cfg.home super.incomplete-dir; - }; - default = - { - download-dir = "${cfg.home}/Downloads"; - incomplete-dir = "${cfg.home}/.incomplete"; - 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; - umask = 18; # 0o022 in decimal as expected by Transmission, obtained with: echo $((8#022)) - utp-enabled = true; - }; - example = - { - download-dir = "/srv/torrents/"; - incomplete-dir = "/srv/torrents/.incomplete/"; - incomplete-dir-enabled = true; - rpc-whitelist = "127.0.0.1,192.168.*.*"; - }; - description = '' - 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 - for documentation. - ''; - }; - - downloadDirPermissions = mkOption { - type = types.str; - default = "770"; - example = "775"; - description = '' - The permissions set by the systemd-tmpfiles-setup service - on settings.download-dir - and settings.incomplete-dir. - ''; - }; - - port = mkOption { - 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 = stateDir; - description = '' - The directory where Transmission will create .config/transmission-daemon/. - as well as Downloads/ unless settings.download-dir is changed, - and .incomplete/ unless settings.incomplete-dir is changed. - ''; - }; - - user = mkOption { - type = types.str; - default = "transmission"; - description = "User account under which Transmission runs."; - }; - - group = mkOption { - type = types.str; - 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 "Whether to automatically open the peer port(s) in the firewall."; - }; - }; - - config = mkIf cfg.enable { - systemd.tmpfiles.rules = - optional (cfg.home != stateDir) "d '${cfg.home}/${settingsDir}' 700 '${cfg.user}' '${cfg.group}' - -" - ++ [ "d '${cfg.settings.download-dir}' '${cfg.downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -" ] - ++ optional cfg.settings.incomplete-dir-enabled - "d '${cfg.settings.incomplete-dir}' '${cfg.downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"; - - assertions = [ - { assertion = builtins.match "^/.*" cfg.home != null; - message = "`services.transmission.home' 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.services.transmission = { - description = "Transmission BitTorrent Service"; - after = [ "network.target" ] ++ optional apparmor "apparmor.service"; - requires = optional apparmor "apparmor.service"; - wantedBy = [ "multi-user.target" ]; - environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source; - preStart = '' - set -eux - ${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' >'${stateDir}/${settingsDir}/settings.json' - ''; - - serviceConfig = { - WorkingDirectory = stateDir; - ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f"; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; - User = cfg.user; - Group = cfg.group; - StateDirectory = removePrefix "/var/lib/" stateDir + "/" + settingsDir; - StateDirectoryMode = "0700"; - BindPaths = - optional (cfg.home != stateDir) "${cfg.home}/${settingsDir}:${stateDir}/${settingsDir}" - ++ [ "${cfg.settings.download-dir}:${stateDir}/Downloads" ] - ++ optional cfg.settings.incomplete-dir-enabled "${cfg.settings.incomplete-dir}:${stateDir}/.incomplete"; - # The following options give: - # systemd-analyze security transmission - # → Overall exposure level for transmission.service: 1.5 OK - AmbientCapabilities = ""; - CapabilityBoundingSet = ""; - LockPersonality = true; - MemoryDenyWriteExecute = true; - NoNewPrivileges = true; - PrivateDevices = true; - PrivateMounts = true; - PrivateNetwork = false; - PrivateTmp = true; - PrivateUsers = false; - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = mkDefault true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectSystem = mkDefault "strict"; - ReadWritePaths = [ stateDir ]; - RemoveIPC = true; - RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - # In case transmission crashes with status=31/SYS, - # having systemd.coredump.enable = true - # and environment.enableDebugInfo = true - # enables to use coredumpctl debug to find the denied syscall. - SystemCallFilter = [ - "@default" - "@aio" - "@basic-io" - #"@chown" - #"@clock" - #"@cpu-emulation" - #"@debug" - "@file-system" - "@io-event" - #"@ipc" - #"@keyring" - #"@memlock" - #"@module" - #"@mount" - "@network-io" - #"@obsolete" - #"@pkey" - #"@privileged" - # Reached when querying infos through RPC (eg. with stig) - "quotactl" - "@process" - #"@raw-io" - #"@reboot" - #"@resources" - #"@setuid" - "@signal" - #"@swap" - "@sync" - "@system-service" - "@timer" - ]; - SystemCallArchitectures = "native"; - UMask = "0077"; - }; - }; - - # It's useful to have transmission in path, e.g. for remote control - environment.systemPackages = [ pkgs.transmission ]; - - users.users = optionalAttrs (cfg.user == "transmission") ({ - transmission = { - group = cfg.group; - uid = config.ids.uids.transmission; - description = "Transmission BitTorrent user"; - home = stateDir; - createHome = false; - }; - }); - - users.groups = optionalAttrs (cfg.group == "transmission") ({ - transmission = { - gid = config.ids.gids.transmission; - }; - }); - - 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 = mkIf cfg.settings.utp-enabled { - "net.core.rmem_max" = mkDefault "4194304"; - "net.core.wmem_max" = mkDefault "1048576"; - }; - - security.apparmor.policies."bin/transmission-daemon".profile = '' - #include - - ${pkgs.transmission}/bin/transmission-daemon { - #include - #include - - ${getLib pkgs.stdenv.cc.cc}/lib/*.so* mr, - ${getLib pkgs.stdenv.cc.libc}/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, - - @{PROC}/sys/kernel/random/uuid r, - @{PROC}/sys/vm/overcommit_memory r, - #@{PROC}/@{pid}/environ r, - @{PROC}/@{pid}/mounts r, - /tmp/tr_session_id_* rwk, - - ${pkgs.openssl.out}/etc/** r, - ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE} r, - ${pkgs.transmission}/share/transmission/** r, - - owner ${stateDir}/${settingsDir}/** rw, - - ${stateDir}/Downloads/** rw, - ${optionalString cfg.settings.incomplete-dir-enabled '' - ${stateDir}/.incomplete/** rw, - ''} - } - ''; - }; - - meta.maintainers = with lib.maintainers; [ julm ]; -} diff --git a/nixos/profiles/hardware/apu2e4.nix b/nixos/profiles/hardware/apu2e4.nix index 6ddf44b..c380c77 100644 --- a/nixos/profiles/hardware/apu2e4.nix +++ b/nixos/profiles/hardware/apu2e4.nix @@ -8,13 +8,6 @@ nix = { }; powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand"; -boot.kernel = { - sysctl = { - # Recommended to improve performance when RAM is sufficient. - "vm.swappiness" = lib.mkDefault 10; - }; -}; - boot.loader = { grub = { enable = true; diff --git a/nixos/profiles/hardware/dl10j.nix b/nixos/profiles/hardware/dl10j.nix index 32a374a..8c92088 100644 --- a/nixos/profiles/hardware/dl10j.nix +++ b/nixos/profiles/hardware/dl10j.nix @@ -8,13 +8,6 @@ nix = { }; powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand"; -boot.kernel = { - sysctl = { - # Recommended to improve performance when RAM is sufficient. - "vm.swappiness" = lib.mkDefault 10; - }; -}; - boot.loader = { grub = { enable = true; diff --git a/nixos/profiles/systems/zramSwap.nix b/nixos/profiles/systems/zramSwap.nix new file mode 100644 index 0000000..bdc7ca7 --- /dev/null +++ b/nixos/profiles/systems/zramSwap.nix @@ -0,0 +1,27 @@ +{ pkgs, lib, config, ... }: +{ +zramSwap = { + enable = true; + algorithm = lib.mkDefault "zstd"; + memoryPercent = lib.mkDefault 50; + swapDevices = lib.mkDefault 1; +}; + +boot.kernel.sysctl = { + # Increase cache pressure, which increases the tendency of the kernel to + # reclaim memory used for caching of directory and inode objects. You will use + # less memory over a longer period of time. The performance hit is negated by + # the downside of swapping sooner. + "vm.vfs_cache_pressure" = lib.mkDefault 500; + + # Increasing how aggressively the kernel will swap memory pages since we are + # using ZRAM first. + "vm.swappiness" = lib.mkDefault 100; + + # Background processes will start writing right away when it hits the 1% limit + "vm.dirty_background_ratio" = lib.mkDefault 1; + + # The system won’t force synchronous I/O until it gets to 50% dirty_ratio. + "vm.dirty_ratio" = lib.mkDefault 50; +}; +} diff --git a/nixpkgs/patches/security.gnupg.diff b/nixpkgs/patches/security.gnupg.diff index ad7bf73..b7b37b9 100644 --- a/nixpkgs/patches/security.gnupg.diff +++ b/nixpkgs/patches/security.gnupg.diff @@ -12,10 +12,10 @@ index f361163ca63..8c199f0fc25 100644 ./security/pam_mount.nix diff --git a/nixos/modules/security/gnupg.nix b/nixos/modules/security/gnupg.nix new file mode 100644 -index 00000000000..a7a3c752267 +index 00000000000..cbf9aad3eae --- /dev/null +++ b/nixos/modules/security/gnupg.nix -@@ -0,0 +1,252 @@ +@@ -0,0 +1,271 @@ +{ config, lib, pkgs, ... }: +let + inherit (builtins) dirOf match split; @@ -179,6 +179,23 @@ index 00000000000..a7a3c752267 + }; +}; +config = lib.mkIf (cfg.secrets != {}) { ++ # Because /run/user/0 is wiped out by pam_systemd when root logouts, ++ # systemd.services.gpg-agent cannot put its socket in ++ # the path expected by gpg: /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts ++ # derived from ${gnupgHome}, since this removal would kill gpg-agent. ++ # ++ # Unfortunately, for reaching such a persistent gpg-agent, ++ # GPG_AGENT_INFO can no longer be used as it is ignored with gpg >= 2.1, ++ # hence three different hacks are done here to make gpg connect ++ # to ${gnupgHome}/S.gpg-agent depending on the concern: ++ # - For gpg-agent, --supervised mode is used to pass it a socket ++ # in the persistent directory gnupgHome. ++ # - For the root user, on its login pam_systemd is mounting a fresh tmpfs on /run/user/0 ++ # so wrong perms are set on /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts ++ # when /run/user/0 is mounted, by overriding user-runtime-dir@.service ++ # - For secret decrypting services, /run/user/0/gnupg ++ # is emptied and keept empty by privately mounting ++ # an empty directory on it, using BindReadOnlyPaths= + systemd.sockets."gpg-agent" = lib.optionalAttrs cfg.enableGpgAgent { + description = "Socket for gpg-agent"; + wantedBy = ["sockets.target"]; @@ -195,6 +212,21 @@ index 00000000000..a7a3c752267 + ln -s -t $out/bin ${pkgs.gnupg}/libexec/gpg-preset-passphrase + ''; in + [ pkgs.gnupg gpgPresetPassphrase ]; ++ systemd.packages = [ ++ # Here, passing by systemd.packages is kind of a hack to be able to ++ # write this file which is neither writable using environment.etc ++ # (because environment.etc."systemd/system".source is set) ++ # nor using systemd.services (because systemd.services."user-runtime-dir@0" ++ # does not exist, and should not to keep using systemd's upstream template ++ # and systemd.services."user-runtime-dir@"). ++ (pkgs.writeTextDir "etc/systemd/system/user-runtime-dir@0.service.d/override.conf" '' ++ [Unit] ++ [Service] ++ ExecStartPost=${pkgs.writeShellScript "redirect-gpg-agent-run-socket" '' ++ install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts ++ ''} ++ '') ++ ]; + systemd.services = + lib.optionalAttrs cfg.enableGpgAgent { + gpg-agent = { @@ -202,18 +234,6 @@ index 00000000000..a7a3c752267 + requires = ["gpg-agent.socket"]; + serviceConfig = { + Type = "simple"; -+ # Because /run/user/0 is wiped out by pam_systemd when root logouts, -+ # systemd.services.gpg-agent cannot put its socket in -+ # the path expected by gpg: -+ # /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts -+ # derived from ${gnupgHome}. -+ # -+ # But GPG_AGENT_INFO is ignored with gpg >= 2.1, -+ # hence this hack makes gpg connect to ${gnupgHome}/S.gpg-agent -+ # by relaxing permissions of the expected socket directory. -+ # With this hack, uploading the passphrase should now be doable with just: -+ # ssh root@example.org 'gpg-preset-passphrase --homedir /var/lib/gnupg --preset $keygrip' -+ ExecStartPre = "${pkgs.coreutils}/bin/install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts"; + ExecStart = ''${pkgs.gnupg}/bin/gpg-agent \ + --supervised \ + --homedir '${gnupgHome}' \ @@ -223,7 +243,7 @@ index 00000000000..a7a3c752267 + ''; + Restart = "on-failure"; + RestartSec = 5; -+ StateDirectory = ["gnupg"]; ++ StateDirectory = ["gnupg" "gnupg/empty"]; + StateDirectoryMode = "700"; + }; + }; @@ -237,8 +257,6 @@ index 00000000000..a7a3c752267 + set -o pipefail + set -eux + decrypt() { -+ # In case /run/user/0 has been cleaned up by pam_systemd -+ install -D -m 640 -d /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts + ${pkgs.gnupg}/bin/gpg --homedir '${gnupgHome}' --no-autostart --batch --decrypt '${secret.gpg}' | + ${lib.optionalString (secret.pipe != null) (secret.pipe+" |")} \ + install -D -m '${secret.mode}' -o '${secret.user}' -g '${secret.group}' /dev/stdin '${secret.path}' @@ -256,8 +274,9 @@ index 00000000000..a7a3c752267 + ''; + serviceConfig = { + Type = "oneshot"; -+ PrivateTmp = true; + RemainAfterExit = true; ++ PrivateTmp = true; ++ BindReadOnlyPaths = [ "/var/lib/gnupg/empty:/run/user/0/gnupg" ]; + } // lib.optionalAttrs (match "^/.*" target == null) { + RuntimeDirectory = lib.removePrefix "/run/" (dirOf secret.path); + RuntimeDirectoryMode = "711"; diff --git a/nixpkgs/patches/transmission+apparmor.diff b/nixpkgs/patches/transmission+apparmor.diff index 9831edd..4efcd44 100644 --- a/nixpkgs/patches/transmission+apparmor.diff +++ b/nixpkgs/patches/transmission+apparmor.diff @@ -1,3 +1,45 @@ +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 @@ -734,10 +776,10 @@ index 688344852ae..339dbd4cb3a 100644 } diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix -index 1bfcf2de82f..a1c6d949bfe 100644 +index 1bfcf2de82f..6fe54000c0d 100644 --- a/nixos/modules/services/torrent/transmission.nix +++ b/nixos/modules/services/torrent/transmission.nix -@@ -1,52 +1,56 @@ +@@ -1,52 +1,50 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, options, ... }: @@ -764,16 +806,13 @@ index 1bfcf2de82f..a1c6d949bfe 100644 - set -ex - cp -f ${settingsFile} ${settingsDir}/settings.json - ''; -+ stateDir = "/var/lib/transmission"; -+ # TODO: switch to configGen.json once RFC0042 is implemented -+ settingsFile = pkgs.writeText "settings.json" (builtins.toJSON (cfg.settings // { -+ download-dir = "${stateDir}/Downloads"; -+ incomplete-dir = "${stateDir}/.incomplete"; -+ })); ++ rootDir = "/run/transmission"; ++ homeDir = "/var/lib/transmission"; + settingsDir = ".config/transmission-daemon"; -+ makeAbsolute = base: path: -+ if builtins.match "^/.*" path == null -+ then base+"/"+path else path; ++ downloadsDir = "Downloads"; ++ incompleteDir = ".incomplete"; ++ # TODO: switch to configGen.json once RFC0042 is implemented ++ settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings); in { options = { @@ -788,32 +827,27 @@ index 1bfcf2de82f..a1c6d949bfe 100644 - 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://${cfg.settings.rpc-bind-address}:${toString cfg.settings.rpc-port}/ by default), ++ 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 ${cfg.settings.download-dir} by default and are ++ 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 = attrs: -+ let super = recursiveUpdate default attrs; in -+ super // { -+ download-dir = makeAbsolute cfg.home super.download-dir; -+ incomplete-dir = makeAbsolute cfg.home super.incomplete-dir; -+ }; ++ apply = recursiveUpdate default; default = { - download-dir = downloadDir; - incomplete-dir = incompleteDir; -+ download-dir = "${cfg.home}/Downloads"; -+ incomplete-dir = "${cfg.home}/.incomplete"; ++ download-dir = "${cfg.home}/${downloadsDir}"; ++ incomplete-dir = "${cfg.home}/${incompleteDir}"; incomplete-dir-enabled = true; + peer-port = 51413; + peer-port-random-high = 65535; @@ -821,12 +855,14 @@ index 1bfcf2de82f..a1c6d949bfe 100644 + peer-port-random-on-start = false; + rpc-bind-address = "127.0.0.1"; + rpc-port = 9091; -+ umask = 18; # 0o022 in decimal as expected by Transmission, obtained with: echo $((8#022)) ++ script-torrent-done-enabled = false; ++ script-torrent-done-filename = ""; ++ umask = 2; # 0o002 in decimal as expected by Transmission + utp-enabled = true; }; example = { -@@ -56,8 +60,9 @@ in +@@ -56,8 +54,9 @@ in rpc-whitelist = "127.0.0.1,192.168.*.*"; }; description = '' @@ -838,15 +874,17 @@ index 1bfcf2de82f..a1c6d949bfe 100644 boolean values must not. See https://github.com/transmission/transmission/wiki/Editing-Configuration-Files -@@ -70,22 +75,30 @@ in +@@ -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 the systemd-tmpfiles-setup service -+ on settings.download-dir ++ 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. ''; }; @@ -867,16 +905,16 @@ index 1bfcf2de82f..a1c6d949bfe 100644 home = mkOption { type = types.path; - default = "/var/lib/transmission"; -+ default = stateDir; ++ default = homeDir; description = '' - The directory where transmission will create files. -+ The directory where Transmission will create .config/transmission-daemon/. -+ as well as Downloads/ unless settings.download-dir is changed, -+ and .incomplete/ unless settings.incomplete-dir is changed. ++ 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 +113,136 @@ in +@@ -100,32 +109,174 @@ in default = "transmission"; description = "Group account under which Transmission runs."; }; @@ -893,25 +931,47 @@ index 1bfcf2de82f..a1c6d949bfe 100644 + }; + + 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 { -- 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}' - -" -+ systemd.tmpfiles.rules = -+ optional (cfg.home != stateDir) "d '${cfg.home}/${settingsDir}' 700 '${cfg.user}' '${cfg.group}' - -" -+ ++ [ "d '${cfg.settings.download-dir}' '${cfg.downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -" ] -+ ++ optional cfg.settings.incomplete-dir-enabled -+ "d '${cfg.settings.incomplete-dir}' '${cfg.downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"; ++ # 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`."; + } @@ -919,11 +979,21 @@ index 1bfcf2de82f..a1c6d949bfe 100644 + { 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"; @@ -942,28 +1012,54 @@ index 1bfcf2de82f..a1c6d949bfe 100644 - # NOTE: transmission has an internal umask that also must be set (in settings.json) - serviceConfig.UMask = "0002"; + serviceConfig = { -+ WorkingDirectory = stateDir; -+ # Use "+" because credentialsFile may not be accessible to the User. ++ # 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 -m 700 -o ${cfg.user} -g ${cfg.group} /dev/stdin '${stateDir}/${settingsDir}/settings.json' ++ 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; -+ StateDirectory = removePrefix "/var/lib/" stateDir + "/" + settingsDir; -+ StateDirectoryMode = "0700"; ++ # 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 = -+ optional (cfg.home != stateDir) "${cfg.home}/${settingsDir}:${stateDir}/${settingsDir}" -+ ++ [ "${cfg.settings.download-dir}:${stateDir}/Downloads" ] -+ ++ optional cfg.settings.incomplete-dir-enabled "${cfg.settings.incomplete-dir}:${stateDir}/.incomplete"; -+ # The following options give: ++ [ "${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 -+ # → Overall exposure level for transmission.service: 1.5 OK + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; ++ # ProtectClock= adds DeviceAllow=char-rtc r ++ DeviceAllow = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; @@ -971,64 +1067,43 @@ index 1bfcf2de82f..a1c6d949bfe 100644 + PrivateMounts = true; + PrivateNetwork = false; + PrivateTmp = true; -+ PrivateUsers = false; ++ PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; -+ ProtectHome = mkDefault 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 = mkDefault "strict"; -+ ReadWritePaths = [ stateDir ]; ++ 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; -+ # In case transmission crashes with status=31/SYS, -+ # having systemd.coredump.enable = true -+ # and environment.enableDebugInfo = true -+ # enables to use coredumpctl debug to find the denied syscall. + SystemCallFilter = [ -+ "@default" -+ "@aio" -+ "@basic-io" -+ #"@chown" -+ #"@clock" -+ #"@cpu-emulation" -+ #"@debug" -+ "@file-system" -+ "@io-event" -+ #"@ipc" -+ #"@keyring" -+ #"@memlock" -+ #"@module" -+ #"@mount" -+ "@network-io" -+ #"@obsolete" -+ #"@pkey" -+ #"@privileged" -+ # Reached when querying infos through RPC (eg. with stig) -+ "quotactl" -+ "@process" -+ #"@raw-io" -+ #"@reboot" -+ #"@resources" -+ #"@setuid" -+ "@signal" -+ #"@swap" -+ "@sync" + "@system-service" -+ "@timer" ++ # 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"; -+ UMask = "0077"; ++ SystemCallErrorNumber = "EPERM"; + }; }; # It's useful to have transmission in path, e.g. for remote control -@@ -133,32 +250,57 @@ in +@@ -133,70 +284,135 @@ in users.users = optionalAttrs (cfg.user == "transmission") ({ transmission = { @@ -1038,7 +1113,7 @@ index 1bfcf2de82f..a1c6d949bfe 100644 description = "Transmission BitTorrent user"; - home = homeDir; - createHome = true; -+ home = stateDir; ++ home = cfg.home; }; }); @@ -1052,6 +1127,7 @@ index 1bfcf2de82f..a1c6d949bfe 100644 - # 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 @@ -1072,62 +1148,133 @@ index 1bfcf2de82f..a1c6d949bfe 100644 + } + ); + -+ # 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. -+ boot.kernel.sysctl = mkIf cfg.settings.utp-enabled { -+ "net.core.rmem_max" = mkDefault "4194304"; # 4MB -+ "net.core.wmem_max" = mkDefault "1048576"; # 1MB -+ }; ++ 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 ++ include ${pkgs.transmission}/bin/transmission-daemon { - #include - #include +- #include +- #include ++ include ++ include - ${getLib pkgs.glibc}/lib/*.so mr, -+ ${getLib pkgs.stdenv.cc.cc}/lib/*.so* mr, -+ ${getLib pkgs.stdenv.cc.libc}/lib/*.so* mr, - ${getLib pkgs.libevent}/lib/libevent*.so* mr, - ${getLib pkgs.curl}/lib/libcurl*.so* mr, - ${getLib pkgs.openssl}/lib/libssl*.so* mr, -@@ -176,27 +318,29 @@ in - ${getLib pkgs.lz4}/lib/liblz4*.so* mr, - ${getLib pkgs.libkrb5}/lib/lib*.so* mr, - ${getLib pkgs.keyutils}/lib/libkeyutils*.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, -+ ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so* mr, -+ ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so* mr, -+ ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.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, -+ #@{PROC}/@{pid}/environ r, -+ @{PROC}/@{pid}/mounts r, -+ /tmp/tr_session_id_* rwk, +- @{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.openssl.out}/etc/** r, -+ ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE} r, - ${pkgs.transmission}/share/transmission/** 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, -+ owner ${stateDir}/${settingsDir}/** rw, - +- - ${fullSettings.download-dir}/** rw, - ${optionalString fullSettings.incomplete-dir-enabled '' - ${fullSettings.incomplete-dir}/** rw, -+ ${stateDir}/Downloads/** rw, ++ owner rw ${cfg.home}/${settingsDir}/**, ++ rw ${cfg.settings.download-dir}/**, + ${optionalString cfg.settings.incomplete-dir-enabled '' -+ ${stateDir}/.incomplete/** rw, ++ 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 } - '') - ]; -- 2.49.0 From df756fe0702ed8b38052cdd4b07c478e70897ff3 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Fri, 31 Jul 2020 05:58:53 +0200 Subject: [PATCH 08/16] ssh: add reverse ssh giving access to losurdo from mermet --- machines/losurdo/fail2ban.nix | 3 +++ machines/losurdo/networking.nix | 3 ++- machines/losurdo/networking/ssh.nix | 18 ++++++++++++++++++ machines/mermet/fail2ban.nix | 3 +++ machines/mermet/networking.nix | 1 + machines/mermet/networking/ssh.nix | 7 +++++++ 6 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 machines/losurdo/networking/ssh.nix create mode 100644 machines/mermet/networking/ssh.nix diff --git a/machines/losurdo/fail2ban.nix b/machines/losurdo/fail2ban.nix index 3b8d2b3..1de9815 100644 --- a/machines/losurdo/fail2ban.nix +++ b/machines/losurdo/fail2ban.nix @@ -2,6 +2,9 @@ { services.sshd.logLevel = "VERBOSE"; services.postgresql.extraConfig = "log_line_prefix = '%h '"; +systemd.services.nftables.postStart = '' + systemctl restart fail2ban +''; services.fail2ban = { enable = true; banaction = "nftables-multiport"; diff --git a/machines/losurdo/networking.nix b/machines/losurdo/networking.nix index cb8ee20..530642b 100644 --- a/machines/losurdo/networking.nix +++ b/machines/losurdo/networking.nix @@ -10,6 +10,7 @@ in { imports = [ networking/nftables.nix + networking/ssh.nix ]; boot.initrd.network = { enable = true; @@ -107,7 +108,7 @@ networking = { nftables.ruleset = '' add rule inet filter input iifname "enp5s0" goto net2fw add rule inet filter output oifname "enp5s0" jump fw2net - add rule inet filter output oifname "enp5s0" log level warn prefix \"fw2net: \" counter drop + add rule inet filter output oifname "enp5s0" log level warn prefix "fw2net: " counter drop add rule inet filter fw2net ip daddr ${lanNet} counter accept comment "LAN" add rule inet filter fw2net ip daddr 224.0.0.0/4 udp dport 1900 counter accept comment "UPnP" ''; diff --git a/machines/losurdo/networking/ssh.nix b/machines/losurdo/networking/ssh.nix new file mode 100644 index 0000000..0e0cbcb --- /dev/null +++ b/machines/losurdo/networking/ssh.nix @@ -0,0 +1,18 @@ +{ pkgs, lib, config, machines, ... }: +{ +systemd.services.ssh-mermet-reverse = { + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + ExecStart = ''${pkgs.openssh}/bin/ssh -v -g -N -T \ + -o ServerAliveInterval=10 \ + -o ExitOnForwardFailure=yes \ + -R *:10022:localhost:22 \ + ${machines.mermet.extraArgs.ipv4} + ''; + Restart = "always"; + RestartSec = "5s"; + }; +}; +} diff --git a/machines/mermet/fail2ban.nix b/machines/mermet/fail2ban.nix index e68efa3..93a9fa6 100644 --- a/machines/mermet/fail2ban.nix +++ b/machines/mermet/fail2ban.nix @@ -1,6 +1,9 @@ { pkgs, lib, config, machines, ... }: { services.sshd.logLevel = "VERBOSE"; +systemd.services.nftables.postStart = '' + systemctl restart fail2ban +''; services.fail2ban = { enable = true; banaction = "nftables-multiport"; diff --git a/machines/mermet/networking.nix b/machines/mermet/networking.nix index e35c4a6..4e24f00 100644 --- a/machines/mermet/networking.nix +++ b/machines/mermet/networking.nix @@ -14,6 +14,7 @@ in { imports = [ networking/nftables.nix + networking/ssh.nix ]; boot.initrd.network = { enable = true; diff --git a/machines/mermet/networking/ssh.nix b/machines/mermet/networking/ssh.nix new file mode 100644 index 0000000..1be605e --- /dev/null +++ b/machines/mermet/networking/ssh.nix @@ -0,0 +1,7 @@ +{ pkgs, lib, config, ... }: +{ +networking.nftables.ruleset = '' + add rule inet filter net2fw tcp dport {10022} counter accept comment "Reverse SSH" +''; +services.openssh.gatewayPorts = "clientspecified"; +} -- 2.49.0 From 10662c4b2528293d336166561e1bd4842b2f627c Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Sat, 1 Aug 2020 07:15:50 +0200 Subject: [PATCH 09/16] wireguard: setup intranet --- machines/losurdo.nix | 6 +++ machines/losurdo/networking.nix | 1 + machines/losurdo/networking/nftables.nix | 6 +++ machines/losurdo/networking/wireguard.nix | 49 +++++++++++++++++++++++ machines/mermet.nix | 6 +++ machines/mermet/networking.nix | 1 + machines/mermet/networking/nftables.nix | 6 +++ machines/mermet/networking/wireguard.nix | 49 +++++++++++++++++++++++ nixpkgs/overlays.nix | 1 + shell.nix | 2 + 10 files changed, 127 insertions(+) create mode 100644 machines/losurdo/networking/wireguard.nix create mode 100644 machines/mermet/networking/wireguard.nix diff --git a/machines/losurdo.nix b/machines/losurdo.nix index 97df904..60bd605 100644 --- a/machines/losurdo.nix +++ b/machines/losurdo.nix @@ -12,6 +12,12 @@ system = "x86_64-linux"; extraArgs = { ipv4 = "80.67.180.251"; + wireguard = { + wg-intranet = { + ipv4 = "192.168.42.2"; + peer = { publicKey = "xsFFep3k8z0pXgUOz4aryOF8l/KPBSOd4WQA26BkXy0="; }; + }; + }; }; modules = [ ../nixos/defaults.nix diff --git a/machines/losurdo/networking.nix b/machines/losurdo/networking.nix index 530642b..8dcee83 100644 --- a/machines/losurdo/networking.nix +++ b/machines/losurdo/networking.nix @@ -11,6 +11,7 @@ in imports = [ networking/nftables.nix networking/ssh.nix + networking/wireguard.nix ]; boot.initrd.network = { enable = true; diff --git a/machines/losurdo/networking/nftables.nix b/machines/losurdo/networking/nftables.nix index b2c5339..e0f1491 100644 --- a/machines/losurdo/networking/nftables.nix +++ b/machines/losurdo/networking/nftables.nix @@ -24,6 +24,12 @@ networking.nftables = { # Some .nix append rules here with: add rule inet filter fw2net ... } + chain intra2fw { + # Some .nix append rules here with: add rule inet filter intra2fw ... + } + chain fw2intra { + # Some .nix append rules here with: add rule inet filter fw2intra ... + } chain input { type filter hook input priority 0 diff --git a/machines/losurdo/networking/wireguard.nix b/machines/losurdo/networking/wireguard.nix new file mode 100644 index 0000000..d307e91 --- /dev/null +++ b/machines/losurdo/networking/wireguard.nix @@ -0,0 +1,49 @@ +{ pkgs, lib, config, machines, machineName, wireguard, ... }: +let + inherit (builtins) hasAttr removeAttrs; + inherit (config.security.gnupg) secrets; + wg = "wg-intranet"; + peers = lib.filterAttrs (peerName: machine: + hasAttr "${wg}" machine.extraArgs.wireguard + ) (removeAttrs machines [machineName]); +in +{ +security.gnupg.secrets."wireguard/${wg}/privateKey" = {}; +systemd.services."wireguard-${wg}" = { + after = [ secrets."wireguard/${wg}/privateKey".service ]; + requires = [ secrets."wireguard/${wg}/privateKey".service ]; +}; +networking.nftables.ruleset = '' + # Allow output connection of ${wg} + add rule inet filter fw2net udp dport ${toString machines.mermet.config.networking.wireguard.interfaces."${wg}".listenPort} counter accept comment "${wg}" + + # Hook ${wg} to input and output chains + add rule inet filter input iifname "${wg}" jump intra2fw + add rule inet filter input iifname "${wg}" log level warn prefix "intra2fw: " counter drop + add rule inet filter output oifname "${wg}" jump fw2intra + add rule inet filter output oifname "${wg}" log level warn prefix "fw2intra: " counter drop + + # ${wg} firewalling + add rule inet filter fw2intra counter accept + add rule inet filter intra2fw ip saddr ${machines.mermet.extraArgs.wireguard."${wg}".ipv4} counter accept comment "mermet" +''; +networking.wireguard.interfaces."${wg}" = { + ips = [ "${wireguard."${wg}".ipv4}/24" ]; + listenPort = 43642; + privateKeyFile = secrets."wireguard/${wg}/privateKey".path; + peers = + lib.mapAttrsToList (peerName: machine: + let peer = machine.config.networking.wireguard.interfaces."${wg}"; in + lib.recursiveUpdate { + allowedIPs = ["${machine.extraArgs.wireguard."${wg}".ipv4}/32"]; + endpoint = "${machine.extraArgs.ipv4}:${toString peer.listenPort}"; + persistentKeepalive = 25; + } machine.extraArgs.wireguard."${wg}".peer + ) peers; + +}; +networking.hosts = lib.mapAttrs' (machineName: machine: lib.nameValuePair + machine.extraArgs.wireguard."${wg}".ipv4 + [ "${machineName}.intranet" ] + ) peers; +} diff --git a/machines/mermet.nix b/machines/mermet.nix index 346400e..bc0d57e 100644 --- a/machines/mermet.nix +++ b/machines/mermet.nix @@ -12,6 +12,12 @@ system = "x86_64-linux"; extraArgs = { ipv4 = "80.67.180.129"; + wireguard = { + wg-intranet = { + ipv4 = "192.168.42.1"; + peer = { publicKey = "XbTEP2X71LBTjmdmySdiOpQJ+uIomcXvg1aiQGUtWBI="; }; + }; + }; }; modules = [ ../nixos/defaults.nix diff --git a/machines/mermet/networking.nix b/machines/mermet/networking.nix index 4e24f00..129b138 100644 --- a/machines/mermet/networking.nix +++ b/machines/mermet/networking.nix @@ -15,6 +15,7 @@ in imports = [ networking/nftables.nix networking/ssh.nix + networking/wireguard.nix ]; boot.initrd.network = { enable = true; diff --git a/machines/mermet/networking/nftables.nix b/machines/mermet/networking/nftables.nix index 46e0b2d..d6f0463 100644 --- a/machines/mermet/networking/nftables.nix +++ b/machines/mermet/networking/nftables.nix @@ -32,6 +32,12 @@ networking.nftables = { accept # Some .nix append rules here with: add rule inet filter fw2lan ... } + chain intra2fw { + # Some .nix append rules here with: add rule inet filter intra2fw ... + } + chain fw2intra { + # Some .nix append rules here with: add rule inet filter fw2intra ... + } chain input { type filter hook input priority 0 diff --git a/machines/mermet/networking/wireguard.nix b/machines/mermet/networking/wireguard.nix new file mode 100644 index 0000000..e464378 --- /dev/null +++ b/machines/mermet/networking/wireguard.nix @@ -0,0 +1,49 @@ +{ pkgs, lib, config, machines, machineName, wireguard, ... }: +let + inherit (builtins) hasAttr removeAttrs; + inherit (config.security.gnupg) secrets; + wg = "wg-intranet"; + listenPort = 43642; + peers = lib.filterAttrs (peerName: machine: + hasAttr "${wg}" machine.extraArgs.wireguard + ) (removeAttrs machines [machineName]); +in +{ +security.gnupg.secrets."wireguard/${wg}/privateKey" = {}; +systemd.services."wireguard-${wg}" = { + after = [ secrets."wireguard/${wg}/privateKey".service ]; + requires = [ secrets."wireguard/${wg}/privateKey".service ]; +}; +networking.nftables.ruleset = '' + # Allow peers to connect to ${wg} + add rule inet filter net2fw udp dport ${toString listenPort} counter accept comment "${wg}" + + # Hook ${wg} to input and output chains + add rule inet filter input iifname "${wg}" jump intra2fw + add rule inet filter input iifname "${wg}" log level warn prefix "intra2fw: " counter drop + add rule inet filter output oifname "${wg}" jump fw2intra + add rule inet filter output oifname "${wg}" log level warn prefix "fw2intra: " counter drop + + # ${wg} firewalling + add rule inet filter fw2intra counter accept + add rule inet filter intra2fw ip saddr ${machines.losurdo.extraArgs.wireguard."${wg}".ipv4} counter accept comment "losurdo" +''; +networking.wireguard.interfaces."${wg}" = { + ips = [ "${wireguard."${wg}".ipv4}/24" ]; + inherit listenPort; + privateKeyFile = secrets."wireguard/${wg}/privateKey".path; + peers = + lib.mapAttrsToList (peerName: machine: + let peer = machine.config.networking.wireguard.interfaces."${wg}"; in + lib.recursiveUpdate { + allowedIPs = ["${machine.extraArgs.wireguard."${wg}".ipv4}/32"]; + endpoint = "${machine.extraArgs.ipv4}:${toString peer.listenPort}"; + persistentKeepalive = 25; + } machine.extraArgs.wireguard."${wg}".peer + ) peers; +}; +networking.hosts = lib.mapAttrs' (machineName: machine: lib.nameValuePair + machine.extraArgs.wireguard."${wg}".ipv4 + [ "${machineName}.intranet" ] + ) peers; +} diff --git a/nixpkgs/overlays.nix b/nixpkgs/overlays.nix index ee22aef..9ff5204 100644 --- a/nixpkgs/overlays.nix +++ b/nixpkgs/overlays.nix @@ -4,4 +4,5 @@ map import overlays/public-inbox.nix overlays/smartctl-tbw.nix overlays/swaplist.nix + overlays/wgsd.nix ] diff --git a/shell.nix b/shell.nix index d7e5386..386c234 100644 --- a/shell.nix +++ b/shell.nix @@ -192,6 +192,8 @@ pkgs.mkShell { pkgs.utillinux #pkgs.zfstools pkgs.linuxPackages.perf + #pkgs.go2nix + pkgs.wireguard ]; #enableParallelBuilding = true; shellHook = '' -- 2.49.0 From dc7563aef6321768e403c07c7ab6ea2f4f3cd460 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Sun, 2 Aug 2020 07:38:15 +0200 Subject: [PATCH 10/16] wireguard: setup in initrd --- machines/losurdo.nix | 10 +++- machines/losurdo/fail2ban.nix | 6 +-- machines/losurdo/fileSystems.nix | 26 ++++----- machines/losurdo/hardware.nix | 8 +-- machines/losurdo/networking.nix | 65 +++++++++++----------- machines/losurdo/networking/nftables.nix | 5 +- machines/losurdo/networking/ssh.nix | 21 ++++++++ machines/losurdo/networking/wireguard.nix | 66 ++++++++++++++++------- machines/losurdo/sanoid.nix | 20 +++---- machines/losurdo/security.nix | 41 ++++++-------- machines/losurdo/syncoid.nix | 24 ++++----- machines/mermet.nix | 12 +++-- machines/mermet/fail2ban.nix | 4 +- machines/mermet/networking/wireguard.nix | 19 ++----- members/julm.nix | 8 ++- nixos/profiles/hardware/dl10j.nix | 2 +- nixos/profiles/systems/zramSwap.nix | 2 + 17 files changed, 195 insertions(+), 144 deletions(-) diff --git a/machines/losurdo.nix b/machines/losurdo.nix index 60bd605..3e89f02 100644 --- a/machines/losurdo.nix +++ b/machines/losurdo.nix @@ -12,10 +12,16 @@ system = "x86_64-linux"; extraArgs = { ipv4 = "80.67.180.251"; - wireguard = { + wireguard = rec { wg-intranet = { ipv4 = "192.168.42.2"; - peer = { publicKey = "xsFFep3k8z0pXgUOz4aryOF8l/KPBSOd4WQA26BkXy0="; }; + listenPort = 43642; + #listenPort = null; + persistentKeepalive = 25; + peer = { + publicKey = "xsFFep3k8z0pXgUOz4aryOF8l/KPBSOd4WQA26BkXy0="; + allowedIPs = [ "${wg-intranet.ipv4}/32" ]; + }; }; }; }; diff --git a/machines/losurdo/fail2ban.nix b/machines/losurdo/fail2ban.nix index 1de9815..edd582d 100644 --- a/machines/losurdo/fail2ban.nix +++ b/machines/losurdo/fail2ban.nix @@ -2,9 +2,9 @@ { services.sshd.logLevel = "VERBOSE"; services.postgresql.extraConfig = "log_line_prefix = '%h '"; -systemd.services.nftables.postStart = '' - systemctl restart fail2ban -''; +/* +systemd.services.nftables.postStart = '' systemctl reload fail2ban ''; +*/ services.fail2ban = { enable = true; banaction = "nftables-multiport"; diff --git a/machines/losurdo/fileSystems.nix b/machines/losurdo/fileSystems.nix index 000ecbc..fb8b124 100644 --- a/machines/losurdo/fileSystems.nix +++ b/machines/losurdo/fileSystems.nix @@ -1,68 +1,68 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machineName, ... }: { imports = [ ../../nixos/profiles/systems/zfs.nix ]; fileSystems."/" = - { device = "losurdo_nvme/root"; + { device = "${machineName}/root"; fsType = "zfs"; }; fileSystems."/home" = - { device = "losurdo_nvme/home"; + { device = "${machineName}/home"; fsType = "zfs"; }; fileSystems."/home/julm" = - { device = "losurdo_nvme/home/julm"; + { device = "${machineName}/home/julm"; fsType = "zfs"; }; fileSystems."/home/julm/work" = - { device = "losurdo_nvme/home/julm/work"; + { device = "${machineName}/home/julm/work"; fsType = "zfs"; }; fileSystems."/nix" = - { device = "losurdo_nvme/nix"; + { device = "${machineName}/nix"; fsType = "zfs"; }; fileSystems."/var" = - { device = "losurdo_nvme/var"; + { device = "${machineName}/var"; fsType = "zfs"; }; fileSystems."/var/cache" = - { device = "losurdo_nvme/var/cache"; + { device = "${machineName}/var/cache"; fsType = "zfs"; }; fileSystems."/var/log" = - { device = "losurdo_nvme/var/log"; + { device = "${machineName}/var/log"; fsType = "zfs"; }; fileSystems."/var/lib/nginx" = - { device = "losurdo_nvme/var/www"; + { device = "${machineName}/var/www"; fsType = "zfs"; }; fileSystems."/var/lib/postgresql" = - { device = "losurdo_nvme/var/postgresql"; + { device = "${machineName}/var/postgresql"; fsType = "zfs"; # with sync=always, # though loading OpenConcerto's initial SQL # takes 1m40s instead of 40s :\ }; fileSystems."/var/lib/transmission" = - { device = "losurdo_nvme/var/torrents"; + { device = "${machineName}/var/torrents"; fsType = "zfs"; }; fileSystems."/var/tmp" = - { device = "losurdo_nvme/var/tmp"; + { device = "${machineName}/var/tmp"; fsType = "zfs"; }; } diff --git a/machines/losurdo/hardware.nix b/machines/losurdo/hardware.nix index 0857044..67a8ed1 100644 --- a/machines/losurdo/hardware.nix +++ b/machines/losurdo/hardware.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machineName, ... }: { imports = [ ../../nixos/profiles/hardware/dl10j.nix @@ -17,17 +17,17 @@ boot.loader.grub.devices = [ ]; fileSystems."/boot" = - { device = "/dev/disk/by-partlabel/losurdo_sd_boot"; + { device = "/dev/disk/by-partlabel/${machineName}_sd_boot"; fsType = "ext2"; }; fileSystems."/boot/efi" = - { device = "/dev/disk/by-partlabel/losurdo_sd_efi"; + { device = "/dev/disk/by-partlabel/${machineName}_sd_efi"; fsType = "vfat"; }; swapDevices = [ - { device = "/dev/disk/by-partlabel/losurdo_nvme_swap"; + { device = "/dev/disk/by-partlabel/${machineName}_nvme_swap"; randomEncryption = { enable = true; cipher = "aes-xts-plain64"; diff --git a/machines/losurdo/networking.nix b/machines/losurdo/networking.nix index 8dcee83..3d508de 100644 --- a/machines/losurdo/networking.nix +++ b/machines/losurdo/networking.nix @@ -1,11 +1,9 @@ -{ pkgs, lib, config, machineName, ... }: +{ pkgs, lib, config, machineName, machines, wireguard, ... }: with builtins; let - inherit (builtins.extraBuiltins) pass-to-file; - inherit (config) networking users; - lanIPv4 = "192.168.1.215"; + #lanIPv4 = "192.168.1.215"; lanNet = "192.168.1.0/24"; - lanIPv4Gateway = "192.168.1.1"; + #lanIPv4Gateway = "192.168.1.1"; in { imports = [ @@ -13,16 +11,10 @@ imports = [ networking/ssh.nix networking/wireguard.nix ]; + boot.initrd.network = { enable = true; - ssh = { - enable = true; - # To prevent ssh from freaking out because a different host key is used, - # a different port for dropbear is useful - # (assuming the same host has also a normal sshd running) - port = 2222; - authorizedKeys = users.users.root.openssh.authorizedKeys.keys; - }; + flushBeforeStage2 = true; # This will automatically load the zfs password prompt on login # and kill the other prompt so boot can continue # The pkill zfs kills the zfs load-key from the console @@ -51,6 +43,7 @@ boot.kernelParams = map ]; */ /* DIY network config, but a right one */ +/* boot.initrd.preLVMCommands = '' set -x @@ -78,6 +71,7 @@ boot.initrd.preLVMCommands = '' # we have to run the postCommands ourselves. ${config.boot.initrd.network.postCommands} ''; +*/ # Workaround https://github.com/NixOS/nixpkgs/issues/56822 #boot.initrd.kernelModules = [ "ipv6" ]; @@ -95,38 +89,39 @@ networking = { domain = "sourcephile.fr"; useDHCP = false; + /* defaultGateway = { address = lanIPv4Gateway; interface = "enp5s0"; }; - /* defaultGateway6 = { address = lanIPv6Gateway; interface = "enp5s0"; }; */ #nameservers = [ ]; - nftables.ruleset = '' - add rule inet filter input iifname "enp5s0" goto net2fw - add rule inet filter output oifname "enp5s0" jump fw2net - add rule inet filter output oifname "enp5s0" log level warn prefix "fw2net: " counter drop - add rule inet filter fw2net ip daddr ${lanNet} counter accept comment "LAN" - add rule inet filter fw2net ip daddr 224.0.0.0/4 udp dport 1900 counter accept comment "UPnP" - ''; - interfaces.enp5s0 = { - useDHCP = false; - ipv4.addresses = [ { address = lanIPv4; prefixLength = 24; } ]; - ipv4.routes = [ { address = networking.defaultGateway.address; prefixLength = 32; } ]; +}; - /* - ipv6.addresses = [ { address = lanIPv6; prefixLength = 64; } - { address = "fe80::1"; prefixLength = 10; } - ]; - ipv6.routes = [ { address = networking.defaultGateway6.address; prefixLength = 64; } ]; - */ - }; - interfaces.wlp4s0 = { - useDHCP = false; - }; +networking.nftables.ruleset = '' + add rule inet filter input iifname "enp5s0" goto net2fw + add rule inet filter output oifname "enp5s0" jump fw2net + add rule inet filter output oifname "enp5s0" log level warn prefix "fw2net: " counter drop + add rule inet filter fw2net ip daddr ${lanNet} counter accept comment "LAN" + add rule inet filter fw2net ip daddr 224.0.0.0/4 udp dport 1900 counter accept comment "UPnP" +''; +networking.interfaces.enp5s0 = { + useDHCP = true; + #ipv4.addresses = [ { address = lanIPv4; prefixLength = 24; } ]; + #ipv4.routes = [ { address = networking.defaultGateway.address; prefixLength = 32; } ]; + + /* + ipv6.addresses = [ { address = lanIPv6; prefixLength = 64; } + { address = "fe80::1"; prefixLength = 10; } + ]; + ipv6.routes = [ { address = networking.defaultGateway6.address; prefixLength = 64; } ]; + */ +}; +networking.interfaces.wlp4s0 = { + useDHCP = false; }; } diff --git a/machines/losurdo/networking/nftables.nix b/machines/losurdo/networking/nftables.nix index e0f1491..137f00c 100644 --- a/machines/losurdo/networking/nftables.nix +++ b/machines/losurdo/networking/nftables.nix @@ -10,6 +10,7 @@ security.lockKernelModules = false; systemd.services.disable-kernel-module-loading.after = [ "nftables.service" ]; # echo -e "$(nix eval machines.losurdo.config.networking.nftables.ruleset)" # nft list ruleset +systemd.services.nftables.serviceConfig.TimeoutStartSec = "20"; networking.nftables = { enable = true; ruleset = lib.mkBefore '' @@ -30,6 +31,9 @@ networking.nftables = { chain fw2intra { # Some .nix append rules here with: add rule inet filter fw2intra ... } + chain fwd-intra { + # Some .nix append rules here with: add rule inet filter fwd-intra ... + } chain input { type filter hook input priority 0 @@ -72,7 +76,6 @@ networking.nftables = { chain forward { type filter hook forward priority 0 policy drop - drop } } ''; diff --git a/machines/losurdo/networking/ssh.nix b/machines/losurdo/networking/ssh.nix index 0e0cbcb..b337e2d 100644 --- a/machines/losurdo/networking/ssh.nix +++ b/machines/losurdo/networking/ssh.nix @@ -1,4 +1,9 @@ { pkgs, lib, config, machines, ... }: +let + inherit (config.security) gnupg; + inherit (config.users) users; + initrdKey = "initrd/ssh.key"; +in { systemd.services.ssh-mermet-reverse = { after = [ "network-online.target" ]; @@ -15,4 +20,20 @@ systemd.services.ssh-mermet-reverse = { RestartSec = "5s"; }; }; + +installer.ssh-nixos.script = lib.mkBefore '' + # Send the SSH key of the initrd + gpg --decrypt '${gnupg.store}/${initrdKey}.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} +''; +boot.initrd.network.ssh = { + enable = true; + # To prevent ssh from freaking out because a different host key is used, + # a different port for dropbear is useful + # (assuming the same host has also a normal sshd running) + port = 2222; + authorizedKeys = users.root.openssh.authorizedKeys.keys; + hostKeys = [ "/root/${initrdKey}" ]; +}; } diff --git a/machines/losurdo/networking/wireguard.nix b/machines/losurdo/networking/wireguard.nix index d307e91..92f93dc 100644 --- a/machines/losurdo/networking/wireguard.nix +++ b/machines/losurdo/networking/wireguard.nix @@ -1,8 +1,9 @@ { pkgs, lib, config, machines, machineName, wireguard, ... }: let inherit (builtins) hasAttr removeAttrs; - inherit (config.security.gnupg) secrets; + inherit (config.security) gnupg; wg = "wg-intranet"; + relay = machines.mermet.extraArgs.wireguard."${wg}"; peers = lib.filterAttrs (peerName: machine: hasAttr "${wg}" machine.extraArgs.wireguard ) (removeAttrs machines [machineName]); @@ -10,14 +11,17 @@ in { security.gnupg.secrets."wireguard/${wg}/privateKey" = {}; systemd.services."wireguard-${wg}" = { - after = [ secrets."wireguard/${wg}/privateKey".service ]; - requires = [ secrets."wireguard/${wg}/privateKey".service ]; + after = [ gnupg.secrets."wireguard/${wg}/privateKey".service ]; + requires = [ gnupg.secrets."wireguard/${wg}/privateKey".service ]; }; networking.nftables.ruleset = '' - # Allow output connection of ${wg} - add rule inet filter fw2net udp dport ${toString machines.mermet.config.networking.wireguard.interfaces."${wg}".listenPort} counter accept comment "${wg}" + # Allow initiating connection for ${wg} + add rule inet filter fw2net udp dport ${toString relay.listenPort} counter accept comment "${wg}" + #add rule inet filter fw2net udp sport ${toString wireguard."${wg}".listenPort} counter accept comment "${wg}" + # Allow peers to initiate connection for ${wg} + #add rule inet filter net2fw udp dport ${toString wireguard."${wg}".listenPort} counter accept comment "${wg}" - # Hook ${wg} to input and output chains + # Hook ${wg} into relevant chains add rule inet filter input iifname "${wg}" jump intra2fw add rule inet filter input iifname "${wg}" log level warn prefix "intra2fw: " counter drop add rule inet filter output oifname "${wg}" jump fw2intra @@ -25,25 +29,49 @@ networking.nftables.ruleset = '' # ${wg} firewalling add rule inet filter fw2intra counter accept - add rule inet filter intra2fw ip saddr ${machines.mermet.extraArgs.wireguard."${wg}".ipv4} counter accept comment "mermet" + add rule inet filter intra2fw ip saddr ${relay.ipv4} counter accept comment "relay" + add rule inet filter forward iifname "${wg}" jump fwd-intra ''; +boot.kernel.sysctl."net.ipv4.ip_forward" = 1; networking.wireguard.interfaces."${wg}" = { ips = [ "${wireguard."${wg}".ipv4}/24" ]; - listenPort = 43642; - privateKeyFile = secrets."wireguard/${wg}/privateKey".path; - peers = - lib.mapAttrsToList (peerName: machine: - let peer = machine.config.networking.wireguard.interfaces."${wg}"; in - lib.recursiveUpdate { - allowedIPs = ["${machine.extraArgs.wireguard."${wg}".ipv4}/32"]; - endpoint = "${machine.extraArgs.ipv4}:${toString peer.listenPort}"; - persistentKeepalive = 25; - } machine.extraArgs.wireguard."${wg}".peer - ) peers; - + listenPort = wireguard."${wg}".listenPort; + privateKeyFile = gnupg.secrets."wireguard/${wg}/privateKey".path; + peers = lib.mapAttrsToList (peerName: machine: + machine.extraArgs.wireguard."${wg}".peer // + { inherit (wireguard."${wg}") persistentKeepalive; } + ) peers; }; networking.hosts = lib.mapAttrs' (machineName: machine: lib.nameValuePair machine.extraArgs.wireguard."${wg}".ipv4 [ "${machineName}.intranet" ] ) peers; + +# Open a wireguard tunnel to a relay +# in case the machine is hosted behind a NAT and has no SSH port forwarding. +# This enables to send the disk password to the initrd, like that: +# ssh -J mermet.sourcephile.fr root@losurdo.intranet -p 2222 +boot.initrd.secrets."/root/initrd/${wg}.key" = "/root/initrd/${wg}.key"; +installer.ssh-nixos.script = '' + # Send the wireguard key of the initrd + gpg --decrypt '${gnupg.store}/wireguard/${wg}/privateKey.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + install -D -m 400 -o root -g root /dev/stdin /root/initrd/${wg}.key +''; +boot.initrd.kernelModules = [ "wireguard" ]; +boot.initrd.extraUtilsCommands = '' + #copy_bin_and_libs ${pkgs.wireguard-tools}/bin/wg + cp -fpdv ${pkgs.wireguard-tools}/bin/.wg-wrapped $out/bin/wg +''; +boot.initrd.network.postCommands = '' + ip link add dev ${wg} type wireguard + ip address add ${wireguard."${wg}".ipv4}/24 dev ${wg} + wg set ${wg} private-key /root/initrd/${wg}.key + ip link set up dev ${wg} + wg set ${wg} peer ${relay.peer.publicKey} \ + endpoint ${machines.mermet.extraArgs.ipv4}:${toString relay.listenPort} \ + allowed-ips ${relay.ipv4}/32 \ + persistent-keepalive 5 + ip route replace ${relay.ipv4}/32 dev ${wg} table main +''; } diff --git a/machines/losurdo/sanoid.nix b/machines/losurdo/sanoid.nix index 7e56649..128f129 100644 --- a/machines/losurdo/sanoid.nix +++ b/machines/losurdo/sanoid.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machineName, ... }: { services.sanoid = { enable = true; @@ -17,41 +17,41 @@ services.sanoid = { #"--debug" ]; datasets = { - "losurdo_nvme/home/julm/work" = { + "${machineName}/home/julm/work" = { use_template = [ "local" ]; daily = 31; }; - "losurdo_nvme/var/postgresql" = { + "${machineName}/var/postgresql" = { use_template = [ "local" ]; daily = 31; }; - "losurdo_nvme/backup/mermet/var/git" = { + "${machineName}/backup/mermet/var/git" = { use_template = [ "remote" ]; daily = 7; }; - "losurdo_nvme/backup/mermet/var/mail" = { + "${machineName}/backup/mermet/var/mail" = { use_template = [ "remote" ]; hourly = 12; daily = 7; }; - "losurdo_nvme/backup/mermet/var/public-inbox" = { + "${machineName}/backup/mermet/var/public-inbox" = { use_template = [ "remote" ]; daily = 7; }; - "losurdo_nvme/backup/mermet/var/redis" = { + "${machineName}/backup/mermet/var/redis" = { use_template = [ "remote" ]; daily = 7; }; - "losurdo_nvme/backup/mermet/var/www" = { + "${machineName}/backup/mermet/var/www" = { use_template = [ "remote" ]; daily = 7; }; - "losurdo_nvme/backup/mermet/home/julm/mail" = { + "${machineName}/backup/mermet/home/julm/mail" = { use_template = [ "remote" ]; hourly = 12; daily = 7; }; - "losurdo_nvme/backup/mermet/home/julm/log" = { + "${machineName}/backup/mermet/home/julm/log" = { use_template = [ "remote" ]; hourly = 12; daily = 7; diff --git a/machines/losurdo/security.nix b/machines/losurdo/security.nix index 6666dd7..c16d9ef 100644 --- a/machines/losurdo/security.nix +++ b/machines/losurdo/security.nix @@ -2,7 +2,6 @@ let inherit (config.security) gnupg; rootKey = "root/key"; - initrdKey = "initrd/ssh.key"; keygrip = "9AA84E6F6D71F9163C46BF396B141A0806219077"; in { @@ -11,6 +10,7 @@ imports = [ ]; security.gnupg.store = builtins.getEnv "PASSWORD_STORE_DIR" + "/machines/${machineName}"; services.openssh.extraConfig = '' + # This is for removing remote gpg-agent's socket StreamLocalBindUnlink yes ''; installer.ssh-nixos = { @@ -19,31 +19,23 @@ installer.ssh-nixos = { #"-R" "/var/lib/gnupg/S.gpg-agent.extra:/run/user/1000/gnupg/d.w1sj57hx3zfcwadyxpr6wko9/S.gpg-agent.extra" #"-o" "StreamLocalBindUnlink=yes" ]; - script = lib.mkMerge [ - (lib.mkBefore '' - # Send the SSH key of the initrd - gpg --decrypt '${gnupg.store}/${initrdKey}.gpg' | - ssh '${config.installer.ssh-nixos.target}' \ - install -D -m 400 -o root -g root /dev/stdin /root/${initrdKey} - '') - (lib.mkBefore '' + script = lib.mkBefore '' + ssh '${config.installer.ssh-nixos.target}' \ + gpg-connect-agent --no-autostart --homedir /var/lib/gnupg "'keyinfo --list'" /bye 2>&1 | + grep -qx -e "gpg-connect-agent: no gpg-agent running in this session" \ + -e "S KEYINFO ${keygrip} . . . 1 .*" || { + # Send the rootKey + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | ssh '${config.installer.ssh-nixos.target}' \ - gpg-connect-agent --no-autostart --homedir /var/lib/gnupg "'keyinfo --list'" /bye 2>&1 | - grep -qx -e "gpg-connect-agent: no gpg-agent running in this session" \ - -e "S KEYINFO ${keygrip} . . . 1 .*" || { - # Send the rootKey - gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | - gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} | - ssh '${config.installer.ssh-nixos.target}' \ - gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import + gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import - # Send the rootKey's passphrase - gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | - ssh '${config.installer.ssh-nixos.target}' \ - gpg-preset-passphrase --homedir /var/lib/gnupg --preset ${keygrip} - } - '') - ]; + # Send the rootKey's passphrase + gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | + ssh '${config.installer.ssh-nixos.target}' \ + gpg-preset-passphrase --homedir /var/lib/gnupg --preset ${keygrip} + } + ''; /* # Send the rootKey gpg --decrypt '${gnupg.store}/${rootKey}.pass.gpg' | @@ -57,5 +49,4 @@ installer.ssh-nixos = { */ }; -boot.initrd.network.ssh.hostKeys = [ "/root/${initrdKey}" ]; } diff --git a/machines/losurdo/syncoid.nix b/machines/losurdo/syncoid.nix index b1d07f1..dc0f21c 100644 --- a/machines/losurdo/syncoid.nix +++ b/machines/losurdo/syncoid.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machineName, ... }: let inherit (config.security) gnupg; in { services.syncoid = { @@ -10,41 +10,41 @@ services.syncoid = { "--create-bookmark" ]; commands = { - "losurdo_nvme/home/julm/work" = { + "${machineName}/home/julm/work" = { sendOptions = "raw"; - target = "root@mermet.sourcephile.fr:rpool/backup/losurdo/home/julm/work"; + target = "root@mermet.sourcephile.fr:rpool/backup/${machineName}/home/julm/work"; }; - "losurdo_nvme/var/postgresql" = { + "${machineName}/var/postgresql" = { sendOptions = "raw"; - target = "root@mermet.sourcephile.fr:rpool/backup/losurdo/var/postgresql"; + target = "root@mermet.sourcephile.fr:rpool/backup/${machineName}/var/postgresql"; }; "root@mermet.sourcephile.fr:rpool/var/mail" = { sendOptions = "raw"; - target = "losurdo_nvme/backup/mermet/var/mail"; + target = "${machineName}/backup/mermet/var/mail"; }; "root@mermet.sourcephile.fr:rpool/var/public-inbox" = { sendOptions = "raw"; - target = "losurdo_nvme/backup/mermet/var/public-inbox"; + target = "${machineName}/backup/mermet/var/public-inbox"; }; "root@mermet.sourcephile.fr:rpool/var/www" = { sendOptions = "raw"; - target = "losurdo_nvme/backup/mermet/var/www"; + target = "${machineName}/backup/mermet/var/www"; }; "root@mermet.sourcephile.fr:rpool/var/git" = { sendOptions = "raw"; - target = "losurdo_nvme/backup/mermet/var/git"; + target = "${machineName}/backup/mermet/var/git"; }; "root@mermet.sourcephile.fr:rpool/var/redis" = { sendOptions = "raw"; - target = "losurdo_nvme/backup/mermet/var/redis"; + target = "${machineName}/backup/mermet/var/redis"; }; "root@mermet.sourcephile.fr:rpool/home/julm/mail" = { sendOptions = "raw"; - target = "losurdo_nvme/backup/mermet/home/julm/mail"; + target = "${machineName}/backup/mermet/home/julm/mail"; }; "root@mermet.sourcephile.fr:rpool/home/julm/log" = { sendOptions = "raw"; - target = "losurdo_nvme/backup/mermet/home/julm/log"; + target = "${machineName}/backup/mermet/home/julm/log"; }; }; }; diff --git a/machines/mermet.nix b/machines/mermet.nix index bc0d57e..e258ad7 100644 --- a/machines/mermet.nix +++ b/machines/mermet.nix @@ -10,12 +10,18 @@ # nix run machines.mermet.installer.ssh-nixos { system = "x86_64-linux"; -extraArgs = { +extraArgs = rec { ipv4 = "80.67.180.129"; - wireguard = { + wireguard = rec { wg-intranet = { ipv4 = "192.168.42.1"; - peer = { publicKey = "XbTEP2X71LBTjmdmySdiOpQJ+uIomcXvg1aiQGUtWBI="; }; + listenPort = 43642; + persistentKeepalive = null; + peer = { + publicKey = "XbTEP2X71LBTjmdmySdiOpQJ+uIomcXvg1aiQGUtWBI="; + allowedIPs = [ "${wg-intranet.ipv4}/32" ]; + endpoint = "${ipv4}:${toString wg-intranet.listenPort}"; + }; }; }; }; diff --git a/machines/mermet/fail2ban.nix b/machines/mermet/fail2ban.nix index 93a9fa6..bc5543b 100644 --- a/machines/mermet/fail2ban.nix +++ b/machines/mermet/fail2ban.nix @@ -1,9 +1,11 @@ { pkgs, lib, config, machines, ... }: { services.sshd.logLevel = "VERBOSE"; +/* systemd.services.nftables.postStart = '' - systemctl restart fail2ban + systemctl reload fail2ban ''; +*/ services.fail2ban = { enable = true; banaction = "nftables-multiport"; diff --git a/machines/mermet/networking/wireguard.nix b/machines/mermet/networking/wireguard.nix index e464378..00cbeb2 100644 --- a/machines/mermet/networking/wireguard.nix +++ b/machines/mermet/networking/wireguard.nix @@ -3,7 +3,6 @@ let inherit (builtins) hasAttr removeAttrs; inherit (config.security.gnupg) secrets; wg = "wg-intranet"; - listenPort = 43642; peers = lib.filterAttrs (peerName: machine: hasAttr "${wg}" machine.extraArgs.wireguard ) (removeAttrs machines [machineName]); @@ -15,10 +14,10 @@ systemd.services."wireguard-${wg}" = { requires = [ secrets."wireguard/${wg}/privateKey".service ]; }; networking.nftables.ruleset = '' - # Allow peers to connect to ${wg} - add rule inet filter net2fw udp dport ${toString listenPort} counter accept comment "${wg}" + # Allow peers to initiate connection for ${wg} + add rule inet filter net2fw udp dport ${toString wireguard."${wg}".listenPort} counter accept comment "${wg}" - # Hook ${wg} to input and output chains + # Hook ${wg} into relevant chains add rule inet filter input iifname "${wg}" jump intra2fw add rule inet filter input iifname "${wg}" log level warn prefix "intra2fw: " counter drop add rule inet filter output oifname "${wg}" jump fw2intra @@ -30,17 +29,9 @@ networking.nftables.ruleset = '' ''; networking.wireguard.interfaces."${wg}" = { ips = [ "${wireguard."${wg}".ipv4}/24" ]; - inherit listenPort; + listenPort = wireguard."${wg}".listenPort; privateKeyFile = secrets."wireguard/${wg}/privateKey".path; - peers = - lib.mapAttrsToList (peerName: machine: - let peer = machine.config.networking.wireguard.interfaces."${wg}"; in - lib.recursiveUpdate { - allowedIPs = ["${machine.extraArgs.wireguard."${wg}".ipv4}/32"]; - endpoint = "${machine.extraArgs.ipv4}:${toString peer.listenPort}"; - persistentKeepalive = 25; - } machine.extraArgs.wireguard."${wg}".peer - ) peers; + peers = lib.mapAttrsToList (peerName: machine: machine.extraArgs.wireguard."${wg}".peer) peers; }; networking.hosts = lib.mapAttrs' (machineName: machine: lib.nameValuePair machine.extraArgs.wireguard."${wg}".ipv4 diff --git a/members/julm.nix b/members/julm.nix index 8f1642d..7de6403 100644 --- a/members/julm.nix +++ b/members/julm.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, wireguard, ... }: let inherit (builtins) readFile; #inherit (builtins.extraBuiltins) pass-chomp; @@ -18,4 +18,10 @@ users.users.julm = { uid = 1000; #uid = userLib.mkUid "julm"; }; +networking.wireguard.interfaces."wg-intranet".peers = [ + { allowedIPs = [ "192.168.42.3/32" ]; + publicKey = "QV5rA6FU7PyTD7nvFI7H/X+zkjhjP5EzVHfODEpj+BM="; + persistentKeepalive = wireguard."wg-intranet".persistentKeepalive; + } +]; } diff --git a/nixos/profiles/hardware/dl10j.nix b/nixos/profiles/hardware/dl10j.nix index 8c92088..7a41cab 100644 --- a/nixos/profiles/hardware/dl10j.nix +++ b/nixos/profiles/hardware/dl10j.nix @@ -13,7 +13,7 @@ boot.loader = { enable = true; version = 2; copyKernels = true; - configurationLimit = 1; + configurationLimit = 3; efiSupport = true; # Because canTouchEfiVariables doesn't work on this system efiInstallAsRemovable = true; diff --git a/nixos/profiles/systems/zramSwap.nix b/nixos/profiles/systems/zramSwap.nix index bdc7ca7..4950175 100644 --- a/nixos/profiles/systems/zramSwap.nix +++ b/nixos/profiles/systems/zramSwap.nix @@ -4,6 +4,8 @@ zramSwap = { enable = true; algorithm = lib.mkDefault "zstd"; memoryPercent = lib.mkDefault 50; + # Linux supports multithreaded compression for 1 device since 3.15. + # See https://lkml.org/lkml/2014/2/28/404 for details. swapDevices = lib.mkDefault 1; }; -- 2.49.0 From 7f2d64f48cd5c7e70c9e8719852797e137f8a4e0 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Sun, 2 Aug 2020 12:52:20 +0200 Subject: [PATCH 11/16] wireguard: improve initrd setup --- machines/losurdo/networking.nix | 4 +++- machines/losurdo/networking/wireguard.nix | 7 ++++++- nixpkgs/patches/fix-flushBeforeStage2.diff | 13 +++++++++++++ shell.nix | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 nixpkgs/patches/fix-flushBeforeStage2.diff diff --git a/machines/losurdo/networking.nix b/machines/losurdo/networking.nix index 3d508de..1ba1cf8 100644 --- a/machines/losurdo/networking.nix +++ b/machines/losurdo/networking.nix @@ -20,7 +20,7 @@ boot.initrd.network = { # The pkill zfs kills the zfs load-key from the console # allowing the boot to continue. postCommands = '' - echo >>/root/.profile "zfs load-key -a && pkill zfs" + echo >>/root/.profile "zfs load-key ${machineName} && pkill zfs" ''; }; @@ -79,10 +79,12 @@ boot.initrd.preLVMCommands = '' # (though / may still be encrypted at this point). # boot.kernelParams = [ "boot.shell_on_fail" ]; +/* # Disable IPv6 entirely until it's available boot.kernel.sysctl = { "net.ipv6.conf.enp5s0.disable_ipv6" = 1; }; +*/ networking = { hostName = machineName; diff --git a/machines/losurdo/networking/wireguard.nix b/machines/losurdo/networking/wireguard.nix index 92f93dc..a260256 100644 --- a/machines/losurdo/networking/wireguard.nix +++ b/machines/losurdo/networking/wireguard.nix @@ -2,6 +2,7 @@ let inherit (builtins) hasAttr removeAttrs; inherit (config.security) gnupg; + inherit (config.boot) initrd; wg = "wg-intranet"; relay = machines.mermet.extraArgs.wireguard."${wg}"; peers = lib.filterAttrs (peerName: machine: @@ -66,7 +67,8 @@ boot.initrd.extraUtilsCommands = '' boot.initrd.network.postCommands = '' ip link add dev ${wg} type wireguard ip address add ${wireguard."${wg}".ipv4}/24 dev ${wg} - wg set ${wg} private-key /root/initrd/${wg}.key + wg set ${wg} private-key /root/initrd/${wg}.key \ + listen-port ${toString wireguard."${wg}".listenPort} ip link set up dev ${wg} wg set ${wg} peer ${relay.peer.publicKey} \ endpoint ${machines.mermet.extraArgs.ipv4}:${toString relay.listenPort} \ @@ -74,4 +76,7 @@ boot.initrd.network.postCommands = '' persistent-keepalive 5 ip route replace ${relay.ipv4}/32 dev ${wg} table main ''; +boot.initrd.postMountCommands = lib.mkIf initrd.network.flushBeforeStage2 '' + ip link del dev ${wg} +''; } diff --git a/nixpkgs/patches/fix-flushBeforeStage2.diff b/nixpkgs/patches/fix-flushBeforeStage2.diff new file mode 100644 index 0000000..3795400 --- /dev/null +++ b/nixpkgs/patches/fix-flushBeforeStage2.diff @@ -0,0 +1,13 @@ +diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix +index 0ab6e626b34..ec794d6eb01 100644 +--- a/nixos/modules/system/boot/initrd-network.nix ++++ b/nixos/modules/system/boot/initrd-network.nix +@@ -139,7 +139,7 @@ in + boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 '' + for iface in $ifaces; do + ip address flush "$iface" +- ip link down "$iface" ++ ip link set "$iface" down + done + ''; + diff --git a/shell.nix b/shell.nix index 386c234..fc58e4c 100644 --- a/shell.nix +++ b/shell.nix @@ -39,6 +39,7 @@ let nixpkgs/patches/installer.ssh-nixos.diff nixpkgs/patches/security.gnupg.diff nixpkgs/patches/services.croc.diff + nixpkgs/patches/fix-flushBeforeStage2.diff ]; # Build nixpkgs with some patches. nixpkgs = originPkgs.applyPatches { -- 2.49.0 From 7ac3b2216bc55d819fea9a45cce774071996d09e Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Mon, 3 Aug 2020 00:00:44 +0200 Subject: [PATCH 12/16] prosody: test on losurdo --- machines/losurdo.nix | 1 + machines/losurdo/prosody.nix | 79 +++++++++++++++++++++++++ machines/mermet/knot/sourcephile.fr.nix | 6 ++ 3 files changed, 86 insertions(+) create mode 100644 machines/losurdo/prosody.nix diff --git a/machines/losurdo.nix b/machines/losurdo.nix index 3e89f02..44055be 100644 --- a/machines/losurdo.nix +++ b/machines/losurdo.nix @@ -36,6 +36,7 @@ modules = [ losurdo/networking.nix losurdo/nginx.nix losurdo/postgresql.nix + losurdo/prosody.nix losurdo/sanoid.nix losurdo/security.nix losurdo/syncoid.nix diff --git a/machines/losurdo/prosody.nix b/machines/losurdo/prosody.nix new file mode 100644 index 0000000..3cac842 --- /dev/null +++ b/machines/losurdo/prosody.nix @@ -0,0 +1,79 @@ +{ pkgs, lib, config, ... }: +let + inherit (config) networking; + inherit (config.users) users; + inherit (config.services) prosody; +in +{ +networking.nftables.ruleset = '' + add rule inet filter net2fw tcp dport {5222, 5269} counter accept comment "XMPP" + add rule inet filter net2fw tcp dport 5000 counter accept comment "XMPP XEP-0065 File Transfer Proxy" + add rule inet filter net2fw tcp dport {${lib.concatMapStringsSep "," toString prosody.httpsPorts}} counter accept comment "XMPP HTTPS" + add rule inet filter fw2net meta skuid ${prosody.user} counter accept comment "Prosody" +''; +users.groups.acme.members = [ users.prosody.name ]; +services.prosody = { + enable = true; + xmppComplianceSuite = true; + modules = { + websocket = false; + limits = false; + groups = true; + announce = true; + welcome = true; + watchregistrations = true; + motd = true; + }; + extraModules = [ + #"net_multiplex" + ]; + extraConfig = '' + Component "proxy65.${networking.domain}" "proxy65" + proxy65_ports = 5000 + ''; + #ports = {80}; + #ssl_ports = {443}; + c2sRequireEncryption = true; + s2sRequireEncryption = true; + s2sSecureAuth = true; + uploadHttp = { + domain = "tmp.${networking.domain}"; + # Prosody's HTTP parser limit on body size + uploadFileSizeLimit = "10485760"; + userQuota = 100 * 1024 * 1024; + uploadExpireAfter = "60 * 60 * 24 * 7"; + }; + muc = [ + { domain = "salons.${networking.domain}"; + extraConfig = '' + restrict_room_creation = "local" + max_history_messages = 42 + muc_room_locking = true + muc_room_lock_timeout = 600 + muc_tombstones = true + muc_tombstone_expiry = 31 * 24 * 60 * 60 + muc_room_default_public = true + muc_room_default_members_only = false + muc_room_default_moderated = true + muc_room_default_public_jids = false + muc_room_default_change_subject = true + muc_room_default_history_length = 42 + muc_room_default_language = "fr" + ''; + } + ]; + virtualHosts."${networking.domain}" = { + enabled = true; + domain = "${networking.domain}"; + ssl.key = "/var/lib/acme/${networking.domain}/key.pem"; + ssl.cert = "/var/lib/acme/${networking.domain}/fullchain.pem"; + }; + admins = [ + "julm@${networking.domain}" + ]; + allowRegistration = false; + authentication = "internal_hashed"; + httpPorts = []; + disco_items = []; +}; +} diff --git a/machines/mermet/knot/sourcephile.fr.nix b/machines/mermet/knot/sourcephile.fr.nix index cd3c7e3..e2b4221 100644 --- a/machines/mermet/knot/sourcephile.fr.nix +++ b/machines/mermet/knot/sourcephile.fr.nix @@ -102,6 +102,7 @@ services.knot.zones."${domain}" = { covid19 A ${machines.mermet.extraArgs.ipv4} openconcerto A ${machines.losurdo.extraArgs.ipv4} croc A ${machines.mermet.extraArgs.ipv4} + xmpp A ${machines.losurdo.extraArgs.ipv4} ; SPF (Sender Policy Framework) @ 3600 IN SPF "v=spf1 mx ip4:${machines.mermet.extraArgs.ipv4} -all" @@ -113,6 +114,11 @@ services.knot.zones."${domain}" = { ; SRV (SeRVice) _git._tcp.git 18000 IN SRV 0 0 9418 git + _xmpp-client._tcp 18000 IN SRV 0 5 5222 xmpp + _xmpp-server._tcp 18000 IN SRV 0 5 5269 xmpp + _xmpp-server._tcp.salons 18000 IN SRV 0 5 5269 xmpp + _xmpp-server._tcp.proxy65 18000 IN SRV 0 5 5000 xmpp + ; CAA (Certificate Authority Authorization) ; DOC: https://blog.qualys.com/ssllabs/2017/03/13/caa-mandated-by-cabrowser-forum @ CAA 128 issue "letsencrypt.org" -- 2.49.0 From c30feae183bfddfc95931d695c96873729740c31 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Mon, 3 Aug 2020 05:37:15 +0200 Subject: [PATCH 13/16] coturn: install on mermet (for prosody) --- machines/losurdo.nix | 4 +-- machines/losurdo/prosody.nix | 29 +++++++++++---- machines/losurdo/users.nix | 16 +++++---- machines/mermet.nix | 5 ++- machines/mermet/coturn.nix | 48 +++++++++++++++++++++++++ machines/mermet/croc.nix | 4 +-- machines/mermet/knot/autogeree.net.nix | 4 +-- machines/mermet/knot/sourcephile.fr.nix | 11 +++--- machines/mermet/postfix.nix | 2 +- 9 files changed, 93 insertions(+), 30 deletions(-) create mode 100644 machines/mermet/coturn.nix diff --git a/machines/losurdo.nix b/machines/losurdo.nix index 44055be..8b72bbf 100644 --- a/machines/losurdo.nix +++ b/machines/losurdo.nix @@ -3,10 +3,8 @@ # Show configuration options with, for example: # nix-instantiate machines/losurdo.nix --eval -A config.networking.hostName # or: -# nix eval machines.losurdo.networking.hostName +# nix eval machines.losurdo.networking.hostName # Install/upgrade with: -# nix run config.installer.ssh-nixos -f machines/losurdo.nix -# or: # nix run machines.losurdo.installer.ssh-nixos { system = "x86_64-linux"; diff --git a/machines/losurdo/prosody.nix b/machines/losurdo/prosody.nix index 3cac842..f55de61 100644 --- a/machines/losurdo/prosody.nix +++ b/machines/losurdo/prosody.nix @@ -1,7 +1,7 @@ { pkgs, lib, config, ... }: let + inherit (builtins.extraBuiltins) pass-chomp; inherit (config) networking; - inherit (config.users) users; inherit (config.services) prosody; in { @@ -9,27 +9,42 @@ networking.nftables.ruleset = '' add rule inet filter net2fw tcp dport {5222, 5269} counter accept comment "XMPP" add rule inet filter net2fw tcp dport 5000 counter accept comment "XMPP XEP-0065 File Transfer Proxy" add rule inet filter net2fw tcp dport {${lib.concatMapStringsSep "," toString prosody.httpsPorts}} counter accept comment "XMPP HTTPS" + add rule inet filter fw2net meta skuid ${prosody.user} tcp dport 3478 counter accept comment "TURN" + add rule inet filter fw2net meta skuid ${prosody.user} udp dport 3478 counter accept comment "TURN" + add rule inet filter fw2net meta skuid ${prosody.user} counter accept comment "Prosody" add rule inet filter fw2net meta skuid ${prosody.user} counter accept comment "Prosody" ''; -users.groups.acme.members = [ users.prosody.name ]; +users.groups.acme.members = [ prosody.user ]; +security.acme.certs."${networking.domain}" = { + postRun = "systemctl reload prosody"; +}; +systemd.services.prosody = { + wants = [ "acme-selfsigned-${networking.domain}.service" "acme-${networking.domain}.service"]; + after = [ "acme-selfsigned-${networking.domain}.service" ]; +}; services.prosody = { enable = true; xmppComplianceSuite = true; modules = { - websocket = false; - limits = false; - groups = true; announce = true; - welcome = true; - watchregistrations = true; + groups = true; + limits = false; motd = true; + watchregistrations = true; + websocket = false; + welcome = true; }; extraModules = [ + "turncredentials" #"net_multiplex" ]; extraConfig = '' Component "proxy65.${networking.domain}" "proxy65" proxy65_ports = 5000 + + turncredentials_host = "turn.${networking.domain}" + turncredentials_secret = "${pass-chomp "machines/mermet/coturn/static-auth-secret"}" + turncredentials_port = 3478 ''; #ports = {80}; #ssl_ports = {443}; diff --git a/machines/losurdo/users.nix b/machines/losurdo/users.nix index 067243c..f226830 100644 --- a/machines/losurdo/users.nix +++ b/machines/losurdo/users.nix @@ -12,13 +12,15 @@ nix.trustedUsers = [ users."julm".name ]; -networking.nftables.ruleset = '' - add rule inet filter fw2net tcp dport {25,465} skuid ${users.julm.name} counter accept comment "SMTP" - add rule inet filter fw2net tcp dport 43 skuid ${users.julm.name} counter accept comment "Whois" - add rule inet filter fw2net tcp dport 6697 skuid ${users.julm.name} counter accept comment "IRCS" - add rule inet filter fw2net tcp dport 11371 skuid ${users.julm.name} counter accept comment "HKP" - add rule inet filter fw2net tcp dport {9009,9010,9011,9012,9013} skuid ${users.julm.name} counter accept comment "croc" -''; +networking.nftables.ruleset = lib.concatMapStringsSep "\n" + (rule: "add rule inet filter fw2net meta skuid ${users.julm.name} " + rule) [ + ''tcp dport {25,465} counter accept comment "SMTP"'' + ''tcp dport 43 counter accept comment "Whois"'' + ''tcp dport 6697 counter accept comment "IRCS"'' + ''tcp dport 5222 counter accept comment "XMPP"'' + ''tcp dport 11371 counter accept comment "HKP"'' + ''tcp dport {9009,9010,9011,9012,9013} counter accept comment "croc"'' +]; users = { mutableUsers = false; diff --git a/machines/mermet.nix b/machines/mermet.nix index e258ad7..0c55c28 100644 --- a/machines/mermet.nix +++ b/machines/mermet.nix @@ -3,10 +3,8 @@ # Show configuration options with, for example: # nix-instantiate machines/mermet.nix --eval -A config.networking.hostName # or: -# nix eval machines.mermet.networking.hostName +# nix eval machines.mermet.networking.hostName # Install/upgrade with: -# nix run config.installer.ssh-nixos -f machines/mermet.nix -# or: # nix run machines.mermet.installer.ssh-nixos { system = "x86_64-linux"; @@ -30,6 +28,7 @@ modules = [ ../nixos/profiles/services/unbound.nix mermet/acme.nix mermet/croc.nix + mermet/coturn.nix mermet/debug.nix mermet/dovecot.nix mermet/fail2ban.nix diff --git a/machines/mermet/coturn.nix b/machines/mermet/coturn.nix new file mode 100644 index 0000000..eb528e1 --- /dev/null +++ b/machines/mermet/coturn.nix @@ -0,0 +1,48 @@ +{ pkgs, lib, config, machineName, ipv4, ... }: +let + inherit (builtins.extraBuiltins) pass-chomp; + inherit (config) networking; + inherit (config.services) coturn; + inherit (config.users) users; +in +{ +networking.nftables.ruleset = '' + add rule inet filter net2fw tcp dport ${toString coturn.listening-port} counter accept comment "TURN" + add rule inet filter net2fw udp dport ${toString coturn.listening-port} counter accept comment "TURN" + add rule inet filter net2fw tcp dport ${toString coturn.tls-listening-port} counter accept comment "TURN TLS" + add rule inet filter net2fw udp dport ${toString coturn.tls-listening-port} counter accept comment "TURN DTLS" + add rule inet filter net2fw tcp dport ${toString coturn.alt-listening-port} counter accept comment "STUN" + add rule inet filter net2fw udp dport ${toString coturn.alt-listening-port} counter accept comment "STUN" + add rule inet filter net2fw udp dport ${toString coturn.min-port}-${toString coturn.max-port} counter accept comment "Relay" +''; +users.groups.acme.members = [ users.turnserver.name ]; +security.acme.certs."${networking.domain}" = { + postRun = "systemctl reload coturn"; +}; +systemd.services.coturn = { + wants = [ "acme-selfsigned-${networking.domain}.service" "acme-${networking.domain}.service"]; + after = [ "acme-selfsigned-${networking.domain}.service" ]; +}; +services.coturn = { + enable = true; + realm = "turn.${networking.domain}"; + use-auth-secret = true; + static-auth-secret = pass-chomp "machines/${machineName}/coturn/static-auth-secret"; + pkey = "/var/lib/acme/${networking.domain}/key.pem"; + cert = "/var/lib/acme/${networking.domain}/fullchain.pem"; + dh-file = toString ../../../sec/openssl/dh.pem; + listening-ips = [ipv4]; + #relay-ips = [ipv4]; + secure-stun = false; + no-cli = false; + cli-ip = "127.0.0.1"; + extraConfig = '' + # Disallow server fingerprinting + prod + # Disallow connections on lo interface + no-loopback-peers + cipher-list="HIGH" + #no-multicast-peers + ''; +}; +} diff --git a/machines/mermet/croc.nix b/machines/mermet/croc.nix index 2b3f939..3458df4 100644 --- a/machines/mermet/croc.nix +++ b/machines/mermet/croc.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machineName, ... }: let inherit (builtins.extraBuiltins) pass-chomp; croc = config.services.croc; @@ -9,6 +9,6 @@ networking.nftables.ruleset = '' ''; services.croc = { enable = true; - pass = pass-chomp "machines/mermet/croc/pass"; + pass = pass-chomp "machines/${machineName}/croc/pass"; }; } diff --git a/machines/mermet/knot/autogeree.net.nix b/machines/mermet/knot/autogeree.net.nix index 4dd2837..76d98c6 100644 --- a/machines/mermet/knot/autogeree.net.nix +++ b/machines/mermet/knot/autogeree.net.nix @@ -7,7 +7,7 @@ let inherit (config) networking; inherit (config.services) knot; inherit (config.security) gnupg; - inherit (config.users) users groups; + inherit (config.users) users; # Use the Git commit time of the ${domain}.nix file to set the serial number. # WARNING: the ${domain}.nix must be committed into Git for this to work. # WARNING: this does not take other .nix into account, though they may contribute to the zone's data. @@ -97,7 +97,7 @@ services.knot.zones."${domain}" = { @ CAA 128 issue "letsencrypt.org" ''; }; -users.users.knot.extraGroups = [ groups.keys.name ]; +users.groups.keys.members = [ users.knot.name ]; services.knot = { keyFiles = [ gnupg.secrets."knot/tsig/${domain}/acme.conf".path ]; }; diff --git a/machines/mermet/knot/sourcephile.fr.nix b/machines/mermet/knot/sourcephile.fr.nix index e2b4221..f404c95 100644 --- a/machines/mermet/knot/sourcephile.fr.nix +++ b/machines/mermet/knot/sourcephile.fr.nix @@ -7,7 +7,7 @@ let inherit (config) networking; inherit (config.security) gnupg; inherit (config.services) knot; - inherit (config.users) users groups; + inherit (config.users) users; # Use the Git commit time of the ${domain}.nix file to set the serial number. # WARNING: the ${domain}.nix must be committed into Git for this to work. # WARNING: this does not take other .nix into account, though they may contribute to the zone's data. @@ -103,6 +103,7 @@ services.knot.zones."${domain}" = { openconcerto A ${machines.losurdo.extraArgs.ipv4} croc A ${machines.mermet.extraArgs.ipv4} xmpp A ${machines.losurdo.extraArgs.ipv4} + turn A ${machines.mermet.extraArgs.ipv4} ; SPF (Sender Policy Framework) @ 3600 IN SPF "v=spf1 mx ip4:${machines.mermet.extraArgs.ipv4} -all" @@ -114,9 +115,9 @@ services.knot.zones."${domain}" = { ; SRV (SeRVice) _git._tcp.git 18000 IN SRV 0 0 9418 git - _xmpp-client._tcp 18000 IN SRV 0 5 5222 xmpp - _xmpp-server._tcp 18000 IN SRV 0 5 5269 xmpp - _xmpp-server._tcp.salons 18000 IN SRV 0 5 5269 xmpp + _xmpp-client._tcp 18000 IN SRV 0 5 5222 xmpp + _xmpp-server._tcp 18000 IN SRV 0 5 5269 xmpp + _xmpp-server._tcp.salons 18000 IN SRV 0 5 5269 xmpp _xmpp-server._tcp.proxy65 18000 IN SRV 0 5 5000 xmpp ; CAA (Certificate Authority Authorization) @@ -124,7 +125,7 @@ services.knot.zones."${domain}" = { @ CAA 128 issue "letsencrypt.org" ''; }; -users.users.knot.extraGroups = [ groups.keys.name ]; +users.groups.keys.members = [ users.knot.name ]; services.knot = { keyFiles = [ gnupg.secrets."knot/tsig/${domain}/acme.conf".path ]; }; diff --git a/machines/mermet/postfix.nix b/machines/mermet/postfix.nix index 658d65f..35545ef 100644 --- a/machines/mermet/postfix.nix +++ b/machines/mermet/postfix.nix @@ -36,7 +36,7 @@ systemd.services.postfix = { networking.nftables.ruleset = '' add rule inet filter net2fw tcp dport 25 counter accept comment "SMTP" add rule inet filter net2fw tcp dport 465 counter accept comment "submissions" - add rule inet filter fw2net tcp dport 25 counter accept comment "SMTP" + add rule inet filter fw2net meta skuid ${postfix.user} tcp dport 25 counter accept comment "SMTP" ''; services.postfix = { enable = true; -- 2.49.0 From 005b807c395eb3f7eb69feac1cf9adc9f6f338c0 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Mon, 3 Aug 2020 08:59:01 +0200 Subject: [PATCH 14/16] prosody: announce tmp.sourcephile.fr for HTTP uploads --- machines/losurdo/prosody.nix | 2 +- machines/mermet/knot/sourcephile.fr.nix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/machines/losurdo/prosody.nix b/machines/losurdo/prosody.nix index f55de61..462f9ad 100644 --- a/machines/losurdo/prosody.nix +++ b/machines/losurdo/prosody.nix @@ -88,7 +88,7 @@ services.prosody = { ]; allowRegistration = false; authentication = "internal_hashed"; - httpPorts = []; + #httpPorts = []; disco_items = []; }; } diff --git a/machines/mermet/knot/sourcephile.fr.nix b/machines/mermet/knot/sourcephile.fr.nix index f404c95..49c6519 100644 --- a/machines/mermet/knot/sourcephile.fr.nix +++ b/machines/mermet/knot/sourcephile.fr.nix @@ -103,6 +103,7 @@ services.knot.zones."${domain}" = { openconcerto A ${machines.losurdo.extraArgs.ipv4} croc A ${machines.mermet.extraArgs.ipv4} xmpp A ${machines.losurdo.extraArgs.ipv4} + tmp A ${machines.losurdo.extraArgs.ipv4} turn A ${machines.mermet.extraArgs.ipv4} ; SPF (Sender Policy Framework) -- 2.49.0 From 1afe273be2233118718649b0c24459009bc1af04 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Tue, 4 Aug 2020 00:20:16 +0200 Subject: [PATCH 15/16] prosody: fix configuration --- machines/losurdo/prosody.nix | 62 ++++++++++++++++++++----- machines/mermet/knot/sourcephile.fr.nix | 2 +- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/machines/losurdo/prosody.nix b/machines/losurdo/prosody.nix index 462f9ad..d551de8 100644 --- a/machines/losurdo/prosody.nix +++ b/machines/losurdo/prosody.nix @@ -1,18 +1,20 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machines, ... }: let inherit (builtins.extraBuiltins) pass-chomp; inherit (config) networking; inherit (config.services) prosody; + inherit (machines.mermet.config.services) coturn; in { +imports = [ + #prosody/biboumi.nix +]; networking.nftables.ruleset = '' - add rule inet filter net2fw tcp dport {5222, 5269} counter accept comment "XMPP" + add rule inet filter net2fw tcp dport {5222,5269} counter accept comment "XMPP" add rule inet filter net2fw tcp dport 5000 counter accept comment "XMPP XEP-0065 File Transfer Proxy" add rule inet filter net2fw tcp dport {${lib.concatMapStringsSep "," toString prosody.httpsPorts}} counter accept comment "XMPP HTTPS" add rule inet filter fw2net meta skuid ${prosody.user} tcp dport 3478 counter accept comment "TURN" add rule inet filter fw2net meta skuid ${prosody.user} udp dport 3478 counter accept comment "TURN" - add rule inet filter fw2net meta skuid ${prosody.user} counter accept comment "Prosody" - add rule inet filter fw2net meta skuid ${prosody.user} counter accept comment "Prosody" ''; users.groups.acme.members = [ prosody.user ]; security.acme.certs."${networking.domain}" = { @@ -22,11 +24,13 @@ systemd.services.prosody = { wants = [ "acme-selfsigned-${networking.domain}.service" "acme-${networking.domain}.service"]; after = [ "acme-selfsigned-${networking.domain}.service" ]; }; +# sudo -u prosody prosodyctl check services.prosody = { enable = true; xmppComplianceSuite = true; modules = { announce = true; + cloud_notify = true; groups = true; limits = false; motd = true; @@ -37,14 +41,41 @@ services.prosody = { extraModules = [ "turncredentials" #"net_multiplex" + #"extdisco" ]; extraConfig = '' - Component "proxy65.${networking.domain}" "proxy65" - proxy65_ports = 5000 - turncredentials_host = "turn.${networking.domain}" turncredentials_secret = "${pass-chomp "machines/mermet/coturn/static-auth-secret"}" turncredentials_port = 3478 + + --external_services = { + -- ["turn.${networking.domain}"] = { + -- port="${toString coturn.alt-listening-port}"; + -- transport="udp"; + -- type="stun"; + -- }; + -- ["turn.${networking.domain}"] = { + -- port="${toString coturn.listening-port}"; + -- transport="udp"; + -- type="turn"; + -- password="${pass-chomp "machines/mermet/coturn/static-auth-secret"}"; + -- -- username=""; + -- }; + --} + --http_files_dir = "/var/lib/prosody/files" + -- http_external_url = "https://tmp.${networking.domain}:5281" + -- https_certificate = "/var/lib/acme/${networking.domain}/fullchain.pem" + -- https_key = "/var/lib/acme/${networking.domain}/key.pem" + -- certificates = "/var/lib/acme" + + proxy65_ports = 5000 + Component "proxy65.${networking.domain}" "proxy65" + proxy65_address = "proxy65.${networking.domain}" + proxy65_acl = { "${networking.domain}" } + + + -- Component "irc.${networking.domain}" + -- component_secret = "useless-secret-on-loopback" ''; #ports = {80}; #ssl_ports = {443}; @@ -57,6 +88,7 @@ services.prosody = { uploadFileSizeLimit = "10485760"; userQuota = 100 * 1024 * 1024; uploadExpireAfter = "60 * 60 * 24 * 7"; + httpUploadPath = "/var/lib/prosody/upload"; }; muc = [ { domain = "salons.${networking.domain}"; @@ -77,18 +109,26 @@ services.prosody = { ''; } ]; + ssl.key = "/var/lib/acme/${networking.domain}/key.pem"; + ssl.cert = "/var/lib/acme/${networking.domain}/fullchain.pem"; + admins = [ + "julm@${networking.domain}" + ]; virtualHosts."${networking.domain}" = { enabled = true; domain = "${networking.domain}"; ssl.key = "/var/lib/acme/${networking.domain}/key.pem"; ssl.cert = "/var/lib/acme/${networking.domain}/fullchain.pem"; }; - admins = [ - "julm@${networking.domain}" - ]; allowRegistration = false; authentication = "internal_hashed"; - #httpPorts = []; + httpPorts = []; + httpsPorts = [5281]; disco_items = []; + package = pkgs.prosody.override { + withCommunityModules = [ + "turncredentials" + ]; + }; }; } diff --git a/machines/mermet/knot/sourcephile.fr.nix b/machines/mermet/knot/sourcephile.fr.nix index 49c6519..614337f 100644 --- a/machines/mermet/knot/sourcephile.fr.nix +++ b/machines/mermet/knot/sourcephile.fr.nix @@ -105,6 +105,7 @@ services.knot.zones."${domain}" = { xmpp A ${machines.losurdo.extraArgs.ipv4} tmp A ${machines.losurdo.extraArgs.ipv4} turn A ${machines.mermet.extraArgs.ipv4} + proxy65 A ${machines.losurdo.extraArgs.ipv4} ; SPF (Sender Policy Framework) @ 3600 IN SPF "v=spf1 mx ip4:${machines.mermet.extraArgs.ipv4} -all" @@ -119,7 +120,6 @@ services.knot.zones."${domain}" = { _xmpp-client._tcp 18000 IN SRV 0 5 5222 xmpp _xmpp-server._tcp 18000 IN SRV 0 5 5269 xmpp _xmpp-server._tcp.salons 18000 IN SRV 0 5 5269 xmpp - _xmpp-server._tcp.proxy65 18000 IN SRV 0 5 5000 xmpp ; CAA (Certificate Authority Authorization) ; DOC: https://blog.qualys.com/ssllabs/2017/03/13/caa-mandated-by-cabrowser-forum -- 2.49.0 From aed5904ec787e2443555d7bd54818d7a07d22562 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Tue, 4 Aug 2020 07:40:29 +0200 Subject: [PATCH 16/16] prosody: more tests of STUN --- machines/losurdo/networking.nix | 2 + machines/losurdo/prosody.nix | 53 +++++++++++++------------ machines/mermet/coturn.nix | 13 +++--- machines/mermet/knot/sourcephile.fr.nix | 5 ++- machines/mermet/networking.nix | 3 +- nixpkgs/overlays.nix | 2 +- nixpkgs/overlays/prosody.nix | 9 +++++ shell.nix | 1 + 8 files changed, 54 insertions(+), 34 deletions(-) create mode 100644 nixpkgs/overlays/prosody.nix diff --git a/machines/losurdo/networking.nix b/machines/losurdo/networking.nix index 1ba1cf8..67cfc52 100644 --- a/machines/losurdo/networking.nix +++ b/machines/losurdo/networking.nix @@ -108,6 +108,8 @@ networking.nftables.ruleset = '' add rule inet filter input iifname "enp5s0" goto net2fw add rule inet filter output oifname "enp5s0" jump fw2net add rule inet filter output oifname "enp5s0" log level warn prefix "fw2net: " counter drop + add rule inet filter fw2net ip daddr ${machines.losurdo.extraArgs.ipv4} counter accept comment "losurdo" + add rule inet filter fw2net ip daddr ${machines.mermet.extraArgs.ipv4} counter accept comment "mermet" add rule inet filter fw2net ip daddr ${lanNet} counter accept comment "LAN" add rule inet filter fw2net ip daddr 224.0.0.0/4 udp dport 1900 counter accept comment "UPnP" ''; diff --git a/machines/losurdo/prosody.nix b/machines/losurdo/prosody.nix index d551de8..153beb2 100644 --- a/machines/losurdo/prosody.nix +++ b/machines/losurdo/prosody.nix @@ -13,6 +13,7 @@ networking.nftables.ruleset = '' add rule inet filter net2fw tcp dport {5222,5269} counter accept comment "XMPP" add rule inet filter net2fw tcp dport 5000 counter accept comment "XMPP XEP-0065 File Transfer Proxy" add rule inet filter net2fw tcp dport {${lib.concatMapStringsSep "," toString prosody.httpsPorts}} counter accept comment "XMPP HTTPS" + add rule inet filter fw2net meta skuid ${prosody.user} counter accept comment "Prosody" add rule inet filter fw2net meta skuid ${prosody.user} tcp dport 3478 counter accept comment "TURN" add rule inet filter fw2net meta skuid ${prosody.user} udp dport 3478 counter accept comment "TURN" ''; @@ -37,43 +38,44 @@ services.prosody = { watchregistrations = true; websocket = false; welcome = true; + proxy65 = false; }; extraModules = [ - "turncredentials" + #"turncredentials" #"net_multiplex" - #"extdisco" + "extdisco" ]; extraConfig = '' - turncredentials_host = "turn.${networking.domain}" - turncredentials_secret = "${pass-chomp "machines/mermet/coturn/static-auth-secret"}" - turncredentials_port = 3478 + --turncredentials_host = "turn.${networking.domain}" + --turncredentials_secret = "${pass-chomp "machines/mermet/coturn/static-auth-secret"}" + --turncredentials_port = 3478 - --external_services = { - -- ["turn.${networking.domain}"] = { - -- port="${toString coturn.alt-listening-port}"; - -- transport="udp"; - -- type="stun"; - -- }; - -- ["turn.${networking.domain}"] = { - -- port="${toString coturn.listening-port}"; - -- transport="udp"; - -- type="turn"; - -- password="${pass-chomp "machines/mermet/coturn/static-auth-secret"}"; - -- -- username=""; - -- }; - --} + external_services = { + ["stun.${networking.domain}"] = { + type="stun"; + transport="udp"; + port="${toString coturn.alt-listening-port}"; + }; + ["turn.${networking.domain}"] = { + type="turn"; + transport="udp"; + port="${toString coturn.listening-port}"; + password="${pass-chomp "machines/mermet/coturn/static-auth-secret"}"; + -- username=""; + }; + } + --http_files_dir = "/var/lib/prosody/files" - -- http_external_url = "https://tmp.${networking.domain}:5281" - -- https_certificate = "/var/lib/acme/${networking.domain}/fullchain.pem" - -- https_key = "/var/lib/acme/${networking.domain}/key.pem" - -- certificates = "/var/lib/acme" + --http_external_url = "https://tmp.${networking.domain}:5281" + --https_certificate = "/var/lib/acme/${networking.domain}/fullchain.pem" + --https_key = "/var/lib/acme/${networking.domain}/key.pem" + --certificates = "/var/lib/acme" proxy65_ports = 5000 Component "proxy65.${networking.domain}" "proxy65" proxy65_address = "proxy65.${networking.domain}" proxy65_acl = { "${networking.domain}" } - -- Component "irc.${networking.domain}" -- component_secret = "useless-secret-on-loopback" ''; @@ -127,7 +129,8 @@ services.prosody = { disco_items = []; package = pkgs.prosody.override { withCommunityModules = [ - "turncredentials" + #"turncredentials" + "extdisco" ]; }; }; diff --git a/machines/mermet/coturn.nix b/machines/mermet/coturn.nix index eb528e1..bc2d7a4 100644 --- a/machines/mermet/coturn.nix +++ b/machines/mermet/coturn.nix @@ -14,11 +14,13 @@ networking.nftables.ruleset = '' add rule inet filter net2fw tcp dport ${toString coturn.alt-listening-port} counter accept comment "STUN" add rule inet filter net2fw udp dport ${toString coturn.alt-listening-port} counter accept comment "STUN" add rule inet filter net2fw udp dport ${toString coturn.min-port}-${toString coturn.max-port} counter accept comment "Relay" + add rule inet filter fw2net meta skuid ${users.turnserver.name} counter accept comment "Prosody" ''; users.groups.acme.members = [ users.turnserver.name ]; security.acme.certs."${networking.domain}" = { - postRun = "systemctl reload coturn"; + postRun = "systemctl try-restart coturn"; }; +environment.systemPackages = [pkgs.coturn]; systemd.services.coturn = { wants = [ "acme-selfsigned-${networking.domain}.service" "acme-${networking.domain}.service"]; after = [ "acme-selfsigned-${networking.domain}.service" ]; @@ -30,19 +32,20 @@ services.coturn = { static-auth-secret = pass-chomp "machines/${machineName}/coturn/static-auth-secret"; pkey = "/var/lib/acme/${networking.domain}/key.pem"; cert = "/var/lib/acme/${networking.domain}/fullchain.pem"; - dh-file = toString ../../../sec/openssl/dh.pem; + dh-file = "${../../../sec/openssl/dh.pem}"; listening-ips = [ipv4]; - #relay-ips = [ipv4]; + relay-ips = [ipv4]; secure-stun = false; no-cli = false; cli-ip = "127.0.0.1"; + cli-password = "none"; extraConfig = '' # Disallow server fingerprinting prod - # Disallow connections on lo interface - no-loopback-peers cipher-list="HIGH" #no-multicast-peers + #fingerprint + verbose ''; }; } diff --git a/machines/mermet/knot/sourcephile.fr.nix b/machines/mermet/knot/sourcephile.fr.nix index 614337f..1786276 100644 --- a/machines/mermet/knot/sourcephile.fr.nix +++ b/machines/mermet/knot/sourcephile.fr.nix @@ -104,6 +104,7 @@ services.knot.zones."${domain}" = { croc A ${machines.mermet.extraArgs.ipv4} xmpp A ${machines.losurdo.extraArgs.ipv4} tmp A ${machines.losurdo.extraArgs.ipv4} + stun A ${machines.mermet.extraArgs.ipv4} turn A ${machines.mermet.extraArgs.ipv4} proxy65 A ${machines.losurdo.extraArgs.ipv4} @@ -115,8 +116,8 @@ services.knot.zones."${domain}" = { @ 180 MX 5 mail ; SRV (SeRVice) - _git._tcp.git 18000 IN SRV 0 0 9418 git - + _git._tcp.git 18000 IN SRV 0 0 9418 git + _stun._udp 18000 IN SRV 0 5 3478 stun _xmpp-client._tcp 18000 IN SRV 0 5 5222 xmpp _xmpp-server._tcp 18000 IN SRV 0 5 5269 xmpp _xmpp-server._tcp.salons 18000 IN SRV 0 5 5269 xmpp diff --git a/machines/mermet/networking.nix b/machines/mermet/networking.nix index 129b138..f125c38 100644 --- a/machines/mermet/networking.nix +++ b/machines/mermet/networking.nix @@ -124,7 +124,8 @@ networking = { #nameservers = [ ]; nftables.ruleset = '' add rule inet filter input iifname "enp1s0" goto net2fw - add rule inet filter output oifname "enp1s0" goto fw2net + add rule inet filter output oifname "enp1s0" jump fw2net + add rule inet filter output oifname "enp1s0" log level warn prefix "fw2net: " counter drop add rule inet filter fw2net ip daddr ${machines.losurdo.extraArgs.ipv4} counter accept comment "losurdo" add rule inet filter input iifname "enp2s0" goto lan2fw diff --git a/nixpkgs/overlays.nix b/nixpkgs/overlays.nix index 9ff5204..4b4d21f 100644 --- a/nixpkgs/overlays.nix +++ b/nixpkgs/overlays.nix @@ -4,5 +4,5 @@ map import overlays/public-inbox.nix overlays/smartctl-tbw.nix overlays/swaplist.nix - overlays/wgsd.nix + overlays/prosody.nix ] diff --git a/nixpkgs/overlays/prosody.nix b/nixpkgs/overlays/prosody.nix new file mode 100644 index 0000000..97b6f28 --- /dev/null +++ b/nixpkgs/overlays/prosody.nix @@ -0,0 +1,9 @@ +self: super: { + prosody = super.prosody.overrideAttrs (oldAttrs: { + communityModules = super.fetchhg { + url = "https://hg.prosody.im/prosody-modules"; + rev = "268fa9d45840"; + sha256 = "1a5ihbg5b0bsw224hdj4fxwy0j98yjzbijz85gd34x923dh3vvkl"; + }; + }); +} diff --git a/shell.nix b/shell.nix index fc58e4c..c327461 100644 --- a/shell.nix +++ b/shell.nix @@ -195,6 +195,7 @@ pkgs.mkShell { pkgs.linuxPackages.perf #pkgs.go2nix pkgs.wireguard + pkgs.stun ]; #enableParallelBuilding = true; shellHook = '' -- 2.49.0