diff options
Diffstat (limited to 'nixos')
33 files changed, 881 insertions, 214 deletions
diff --git a/nixos/doc/manual/release-notes/rl-1809.xml b/nixos/doc/manual/release-notes/rl-1809.xml index a70fb1aedbb8..d527984f5ef1 100644 --- a/nixos/doc/manual/release-notes/rl-1809.xml +++ b/nixos/doc/manual/release-notes/rl-1809.xml @@ -190,6 +190,16 @@ $ nix-instantiate -E '(import <nixpkgsunstable> {}).gitFull' which indicates that the nix output hash will be used as tag. </para> </listitem> + <listitem> + <para> + Options + <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.ramfsMountPoint</literal> + <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.storage.mountPoint</literal> + were removed. <literal>luksroot.nix</literal> module never supported more than one YubiKey at + a time anyway, hence those options never had any effect. You should be able to remove them + from your config without any issues. + </para> + </listitem> </itemizedlist> </section> diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix index 398660967c52..9dc4749b08d1 100644 --- a/nixos/modules/config/shells-environment.nix +++ b/nixos/modules/config/shells-environment.nix @@ -70,7 +70,7 @@ in description = '' Shell script code called during global environment initialisation after all variables and profileVariables have been set. - This code is asumed to be shell-independent, which means you should + This code is assumed to be shell-independent, which means you should stick to pure sh without sh word split. ''; type = types.lines; diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index 40445c3b960a..bffd8aff78b9 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -325,6 +325,7 @@ hydron = 298; cfssl = 299; cassandra = 300; + qemu-libvirtd = 301; # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399! @@ -610,6 +611,7 @@ hydron = 298; cfssl = 299; cassandra = 300; + qemu-libvirtd = 301; # When adding a gid, make sure it doesn't match an existing # uid. Users and groups with the same name should have equal diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix index 3be12e4a6375..63717e0c6a81 100644 --- a/nixos/modules/misc/version.nix +++ b/nixos/modules/misc/version.nix @@ -76,9 +76,6 @@ in config = { - warnings = lib.optional (options.system.stateVersion.highestPrio > 1000) - "You don't have `system.stateVersion` explicitly set. Expect things to break."; - system.nixos = { # These defaults are set here rather than up there so that # changing them would not rebuild the manual diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 73173dd4e24b..e19853efd73c 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -623,6 +623,7 @@ ./services/search/hound.nix ./services/search/kibana.nix ./services/search/solr.nix + ./services/security/certmgr.nix ./services/security/cfssl.nix ./services/security/clamav.nix ./services/security/fail2ban.nix diff --git a/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix index b995d390b279..f4df4e983e42 100644 --- a/nixos/modules/programs/zsh/oh-my-zsh.nix +++ b/nixos/modules/programs/zsh/oh-my-zsh.nix @@ -3,7 +3,30 @@ with lib; let + cfg = config.programs.zsh.ohMyZsh; + + mkLinkFarmEntry = name: dir: + let + env = pkgs.buildEnv { + name = "zsh-${name}-env"; + paths = cfg.customPkgs; + pathsToLink = "/share/zsh/${dir}"; + }; + in + { inherit name; path = "${env}/share/zsh/${dir}"; }; + + mkLinkFarmEntry' = name: mkLinkFarmEntry name name; + + custom = + if cfg.custom != null then cfg.custom + else if length cfg.customPkgs == 0 then null + else pkgs.linkFarm "oh-my-zsh-custom" [ + (mkLinkFarmEntry' "themes") + (mkLinkFarmEntry "completions" "site-functions") + (mkLinkFarmEntry' "plugins") + ]; + in { options = { @@ -34,10 +57,19 @@ in }; custom = mkOption { - default = ""; - type = types.str; + default = null; + type = with types; nullOr str; description = '' Path to a custom oh-my-zsh package to override config of oh-my-zsh. + (Can't be used along with `customPkgs`). + ''; + }; + + customPkgs = mkOption { + default = []; + type = types.listOf types.package; + description = '' + List of custom packages that should be loaded into `oh-my-zsh`. ''; }; @@ -67,7 +99,7 @@ in environment.systemPackages = [ cfg.package ]; - programs.zsh.interactiveShellInit = with builtins; '' + programs.zsh.interactiveShellInit = '' # oh-my-zsh configuration generated by NixOS export ZSH=${cfg.package}/share/oh-my-zsh @@ -75,8 +107,8 @@ in "plugins=(${concatStringsSep " " cfg.plugins})" } - ${optionalString (stringLength(cfg.custom) > 0) - "ZSH_CUSTOM=\"${cfg.custom}\"" + ${optionalString (custom != null) + "ZSH_CUSTOM=\"${custom}\"" } ${optionalString (stringLength(cfg.theme) > 0) @@ -92,5 +124,15 @@ in source $ZSH/oh-my-zsh.sh ''; + + assertions = [ + { + assertion = cfg.custom != null -> cfg.customPkgs == []; + message = "If `cfg.custom` is set for `ZSH_CUSTOM`, `customPkgs` can't be used!"; + } + ]; + }; + + meta.doc = ./oh-my-zsh.xml; } diff --git a/nixos/modules/programs/zsh/oh-my-zsh.xml b/nixos/modules/programs/zsh/oh-my-zsh.xml new file mode 100644 index 000000000000..b74da8630ee7 --- /dev/null +++ b/nixos/modules/programs/zsh/oh-my-zsh.xml @@ -0,0 +1,125 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="module-programs-zsh-ohmyzsh"> + +<title>Oh my ZSH</title> + +<para><literal><link xlink:href="https://ohmyz.sh/">oh-my-zsh</link></literal> is a framework +to manage your <link xlink:href="https://www.zsh.org/">ZSH</link> configuration +including completion scripts for several CLI tools or custom prompt themes.</para> + +<section><title>Basic usage</title> +<para>The module uses the <literal>oh-my-zsh</literal> package with all available features. The +initial setup using Nix expressions is fairly similar to the configuration format +of <literal>oh-my-zsh</literal>. + +<programlisting> +{ + programs.ohMyZsh = { + enable = true; + plugins = [ "git" "python" "man" ]; + theme = "agnoster"; + }; +} +</programlisting> + +For a detailed explanation of these arguments please refer to the +<link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki"><literal>oh-my-zsh</literal> docs</link>. +</para> +<para>The expression generates the needed +configuration and writes it into your <literal>/etc/zshrc</literal>. +</para></section> + +<section><title>Custom additions</title> + +<para>Sometimes third-party or custom scripts such as a modified theme may be needed. +<literal>oh-my-zsh</literal> provides the +<link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals"><literal>ZSH_CUSTOM</literal></link> +environment variable for this which points to a directory with additional scripts.</para> + +<para>The module can do this as well: + +<programlisting> +{ + programs.ohMyZsh.custom = "~/path/to/custom/scripts"; +} +</programlisting> +</para></section> + +<section><title>Custom environments</title> + +<para>There are several extensions for <literal>oh-my-zsh</literal> packaged in <literal>nixpkgs</literal>. +One of them is <link xlink:href="https://github.com/spwhitt/nix-zsh-completions">nix-zsh-completions</link> +which bundles completion scripts and a plugin for <literal>oh-my-zsh</literal>.</para> + +<para>Rather than using a single mutable path for <literal>ZSH_CUSTOM</literal>, it's also possible to +generate this path from a list of Nix packages: + +<programlisting> +{ pkgs, ... }: +{ + programs.ohMyZsh.customPkgs = with pkgs; [ + pkgs.nix-zsh-completions + # and even more... + ]; +} +</programlisting> + +Internally a single store path will be created using <literal>buildEnv</literal>. +Please refer to the docs of +<link xlink:href="https://nixos.org/nixpkgs/manual/#sec-building-environment"><literal>buildEnv</literal></link> +for further reference.</para> + +<para><emphasis>Please keep in mind that this is not compatible with <literal>programs.ohMyZsh.custom</literal> +as it requires an immutable store path while <literal>custom</literal> shall remain mutable! An evaluation failure +will be thrown if both <literal>custom</literal> and <literal>customPkgs</literal> are set.</emphasis> +</para></section> + +<section><title>Package your own customizations</title> + +<para>If third-party customizations (e.g. new themes) are supposed to be added to <literal>oh-my-zsh</literal> +there are several pitfalls to keep in mind:</para> + +<itemizedlist> + <listitem> + <para>To comply with the default structure of <literal>ZSH</literal> the entire output needs to be written to + <literal>$out/share/zsh.</literal></para> + </listitem> + <listitem> + <para>Completion scripts are supposed to be stored at <literal>$out/share/zsh/site-functions</literal>. This directory + is part of the <literal><link xlink:href="http://zsh.sourceforge.net/Doc/Release/Functions.html">fpath</link></literal> + and the package should be compatible with pure <literal>ZSH</literal> setups. The module will automatically link + the contents of <literal>site-functions</literal> to completions directory in the proper store path.</para> + </listitem> + <listitem> + <para>The <literal>plugins</literal> directory needs the structure <literal>pluginname/pluginname.plugin.zsh</literal> + as structured in the <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/tree/91b771914bc7c43dd7c7a43b586c5de2c225ceb7/plugins">upstream repo.</link> + </para> + </listitem> +</itemizedlist> + +<para> +A derivation for <literal>oh-my-zsh</literal> may look like this: +<programlisting> +{ stdenv, fetchFromGitHub }: + +stdenv.mkDerivation rec { + name = "exemplary-zsh-customization-${version}"; + version = "1.0.0"; + src = fetchFromGitHub { + # path to the upstream repository + }; + + dontBuild = true; + installPhase = '' + mkdir -p $out/share/zsh/site-functions + cp {themes,plugins} $out/share/zsh + cp completions $out/share/zsh/site-functions + ''; +} +</programlisting> +</para> +</section> +</chapter> diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 75f02ea78e64..f032f10e4557 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -256,6 +256,7 @@ with lib; (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "") (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "") (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.") + (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.") # ZSH (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ]) diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix index c5a000d5c6a7..dafb0af20756 100644 --- a/nixos/modules/services/desktops/geoclue2.nix +++ b/nixos/modules/services/desktops/geoclue2.nix @@ -4,6 +4,10 @@ with lib; +let + # the demo agent isn't built by default, but we need it here + package = pkgs.geoclue2.override { withDemoAgent = config.services.geoclue2.enableDemoAgent; }; +in { ###### interface @@ -21,21 +25,42 @@ with lib; ''; }; + enableDemoAgent = mkOption { + type = types.bool; + default = true; + description = '' + Whether to use the GeoClue demo agent. This should be + overridden by desktop environments that provide their own + agent. + ''; + }; + }; }; ###### implementation - config = mkIf config.services.geoclue2.enable { - environment.systemPackages = [ pkgs.geoclue2 ]; - - services.dbus.packages = [ pkgs.geoclue2 ]; - - systemd.packages = [ pkgs.geoclue2 ]; - + environment.systemPackages = [ package ]; + + services.dbus.packages = [ package ]; + + systemd.packages = [ package ]; + + # this needs to run as a user service, since it's associated with the + # user who is making the requests + systemd.user.services = mkIf config.services.geoclue2.enableDemoAgent { + "geoclue-agent" = { + description = "Geoclue agent"; + script = "${package}/libexec/geoclue-2.0/demos/agent"; + # this should really be `partOf = [ "geoclue.service" ]`, but + # we can't be part of a system service, and the agent should + # be okay with the main service coming and going + wantedBy = [ "default.target" ]; + }; + }; }; } diff --git a/nixos/modules/services/editors/infinoted.nix b/nixos/modules/services/editors/infinoted.nix index bba21caca85d..9cc8d421270e 100644 --- a/nixos/modules/services/editors/infinoted.nix +++ b/nixos/modules/services/editors/infinoted.nix @@ -10,8 +10,8 @@ in { package = mkOption { type = types.package; - default = pkgs.libinfinity.override { daemon = true; }; - defaultText = "pkgs.libinfinity.override { daemon = true; }"; + default = pkgs.libinfinity; + defaultText = "pkgs.libinfinity"; description = '' Package providing infinoted ''; @@ -119,7 +119,7 @@ in { users.groups = optional (cfg.group == "infinoted") { name = "infinoted"; }; - + systemd.services.infinoted = { description = "Gobby Dedicated Server"; @@ -129,7 +129,7 @@ in { serviceConfig = { Type = "simple"; Restart = "always"; - ExecStart = "${cfg.package}/bin/infinoted-${versions.majorMinor cfg.package.version} --config-file=/var/lib/infinoted/infinoted.conf"; + ExecStart = "${cfg.package.infinoted} --config-file=/var/lib/infinoted/infinoted.conf"; User = cfg.user; Group = cfg.group; PermissionsStartOnly = true; diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index de0aa1a2c2c3..019c8fd9ec48 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -161,8 +161,8 @@ in { description = "DHCP Client"; wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target"; - after = [ "network.target" ]; wants = [ "network.target" ]; + before = [ "network.target" ]; # Stopping dhcpcd during a reconfiguration is undesirable # because it brings down the network interfaces configured by diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 4bae05b6dd30..c788528fa47b 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -8,6 +8,7 @@ let ${optionalString cfg.userControlled.enable '' ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=${cfg.userControlled.group} update_config=1''} + ${cfg.extraConfig} ${concatStringsSep "\n" (mapAttrsToList (ssid: config: with config; let key = if psk != null then ''"${psk}"'' @@ -165,6 +166,17 @@ in { description = "Members of this group can control wpa_supplicant."; }; }; + extraConfig = mkOption { + type = types.str; + default = ""; + example = '' + p2p_disabled=1 + ''; + description = '' + Extra lines appended to the configuration file. + See wpa_supplicant.conf(5) for available options. + ''; + }; }; }; diff --git a/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix index 4c1ee75d536c..a4cd368397e7 100644 --- a/nixos/modules/services/networking/zerotierone.nix +++ b/nixos/modules/services/networking/zerotierone.nix @@ -17,6 +17,15 @@ in ''; }; + options.services.zerotierone.port = mkOption { + default = 9993; + example = 9993; + type = types.int; + description = '' + Network port used by ZeroTier. + ''; + }; + options.services.zerotierone.package = mkOption { default = pkgs.zerotierone; defaultText = "pkgs.zerotierone"; @@ -40,7 +49,7 @@ in touch "/var/lib/zerotier-one/networks.d/${netId}.conf" '') cfg.joinNetworks); serviceConfig = { - ExecStart = "${cfg.package}/bin/zerotier-one"; + ExecStart = "${cfg.package}/bin/zerotier-one -p${toString cfg.port}"; Restart = "always"; KillMode = "process"; }; @@ -49,8 +58,8 @@ in # ZeroTier does not issue DHCP leases, but some strangers might... networking.dhcpcd.denyInterfaces = [ "zt*" ]; - # ZeroTier receives UDP transmissions on port 9993 by default - networking.firewall.allowedUDPPorts = [ 9993 ]; + # ZeroTier receives UDP transmissions + networking.firewall.allowedUDPPorts = [ cfg.port ]; environment.systemPackages = [ cfg.package ]; }; diff --git a/nixos/modules/services/security/certmgr.nix b/nixos/modules/services/security/certmgr.nix new file mode 100644 index 000000000000..22d5817ec4f0 --- /dev/null +++ b/nixos/modules/services/security/certmgr.nix @@ -0,0 +1,194 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.certmgr; + + specs = mapAttrsToList (n: v: rec { + name = n + ".json"; + path = if isAttrs v then pkgs.writeText name (builtins.toJSON v) else v; + }) cfg.specs; + + allSpecs = pkgs.linkFarm "certmgr.d" specs; + + certmgrYaml = pkgs.writeText "certmgr.yaml" (builtins.toJSON { + dir = allSpecs; + default_remote = cfg.defaultRemote; + svcmgr = cfg.svcManager; + before = cfg.validMin; + interval = cfg.renewInterval; + inherit (cfg) metricsPort metricsAddress; + }); + + specPaths = map dirOf (concatMap (spec: + if isAttrs spec then + collect isString (filterAttrsRecursive (n: v: isAttrs v || n == "path") spec) + else + [ spec ] + ) (attrValues cfg.specs)); + + preStart = '' + ${concatStringsSep " \\\n" (["mkdir -p"] ++ map escapeShellArg specPaths)} + ${pkgs.certmgr}/bin/certmgr -f ${certmgrYaml} check + ''; +in +{ + options.services.certmgr = { + enable = mkEnableOption "certmgr"; + + defaultRemote = mkOption { + type = types.str; + default = "127.0.0.1:8888"; + description = "The default CA host:port to use."; + }; + + validMin = mkOption { + default = "72h"; + type = types.str; + description = "The interval before a certificate expires to start attempting to renew it."; + }; + + renewInterval = mkOption { + default = "30m"; + type = types.str; + description = "How often to check certificate expirations and how often to update the cert_next_expires metric."; + }; + + metricsAddress = mkOption { + default = "127.0.0.1"; + type = types.str; + description = "The address for the Prometheus HTTP endpoint."; + }; + + metricsPort = mkOption { + default = 9488; + type = types.ints.u16; + description = "The port for the Prometheus HTTP endpoint."; + }; + + specs = mkOption { + default = {}; + example = literalExample '' + { + exampleCert = + let + domain = "example.com"; + secret = name: "/var/lib/secrets/''${name}.pem"; + in { + service = "nginx"; + action = "reload"; + authority = { + file.path = secret "ca"; + }; + certificate = { + path = secret domain; + }; + private_key = { + owner = "root"; + group = "root"; + mode = "0600"; + path = secret "''${domain}-key"; + }; + request = { + CN = domain; + hosts = [ "mail.''${domain}" "www.''${domain}" ]; + key = { + algo = "rsa"; + size = 2048; + }; + names = { + O = "Example Organization"; + C = "USA"; + }; + }; + }; + otherCert = "/var/certmgr/specs/other-cert.json"; + } + ''; + type = with types; attrsOf (either (submodule { + options = { + service = mkOption { + type = nullOr str; + default = null; + description = "The service on which to perform <action> after fetching."; + }; + + action = mkOption { + type = addCheck str (x: cfg.svcManager == "command" || elem x ["restart" "reload" "nop"]); + default = "nop"; + description = "The action to take after fetching."; + }; + + # These ought all to be specified according to certmgr spec def. + authority = mkOption { + type = attrs; + description = "certmgr spec authority object."; + }; + + certificate = mkOption { + type = nullOr attrs; + description = "certmgr spec certificate object."; + }; + + private_key = mkOption { + type = nullOr attrs; + description = "certmgr spec private_key object."; + }; + + request = mkOption { + type = nullOr attrs; + description = "certmgr spec request object."; + }; + }; + }) path); + description = '' + Certificate specs as described by: + <link xlink:href="https://github.com/cloudflare/certmgr#certificate-specs" /> + These will be added to the Nix store, so they will be world readable. + ''; + }; + + svcManager = mkOption { + default = "systemd"; + type = types.enum [ "circus" "command" "dummy" "openrc" "systemd" "sysv" ]; + description = '' + This specifies the service manager to use for restarting or reloading services. + See: <link xlink:href="https://github.com/cloudflare/certmgr#certmgryaml" />. + For how to use the "command" service manager in particular, + see: <link xlink:href="https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it" />. + ''; + }; + + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.specs != {}; + message = "Certmgr specs cannot be empty."; + } + { + assertion = !any (hasAttrByPath [ "authority" "auth_key" ]) (attrValues cfg.specs); + message = '' + Inline services.certmgr.specs are added to the Nix store rendering them world readable. + Specify paths as specs, if you want to use include auth_key - or use the auth_key_file option." + ''; + } + ]; + + systemd.services.certmgr = { + description = "certmgr"; + path = mkIf (cfg.svcManager == "command") [ pkgs.bash ]; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + inherit preStart; + + serviceConfig = { + Restart = "always"; + RestartSec = "10s"; + ExecStart = "${pkgs.certmgr}/bin/certmgr -f ${certmgrYaml}"; + }; + }; + }; +} diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix index 47c70cf0687b..0b28bc894458 100644 --- a/nixos/modules/services/security/vault.nix +++ b/nixos/modules/services/security/vault.nix @@ -1,6 +1,7 @@ { config, lib, pkgs, ... }: with lib; + let cfg = config.services.vault; @@ -24,15 +25,22 @@ let ${cfg.telemetryConfig} } ''} + ${cfg.extraConfig} ''; in + { options = { - services.vault = { - enable = mkEnableOption "Vault daemon"; + package = mkOption { + type = types.package; + default = pkgs.vault; + defaultText = "pkgs.vault"; + description = "This option specifies the vault package to use."; + }; + address = mkOption { type = types.str; default = "127.0.0.1:8200"; @@ -58,7 +66,7 @@ in default = '' tls_min_version = "tls12" ''; - description = "extra configuration"; + description = "Extra text appended to the listener section."; }; storageBackend = mkOption { @@ -84,6 +92,12 @@ in default = ""; description = "Telemetry configuration"; }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "Extra text appended to <filename>vault.hcl</filename>."; + }; }; }; @@ -122,7 +136,7 @@ in User = "vault"; Group = "vault"; PermissionsStartOnly = true; - ExecStart = "${pkgs.vault}/bin/vault server -config ${configFile}"; + ExecStart = "${cfg.package}/bin/vault server -config ${configFile}"; PrivateDevices = true; PrivateTmp = true; ProtectSystem = "full"; diff --git a/nixos/modules/services/system/localtime.nix b/nixos/modules/services/system/localtime.nix index b9355bbb9441..c7e897c96448 100644 --- a/nixos/modules/services/system/localtime.nix +++ b/nixos/modules/services/system/localtime.nix @@ -22,14 +22,8 @@ in { config = mkIf cfg.enable { services.geoclue2.enable = true; - security.polkit.extraConfig = '' - polkit.addRule(function(action, subject) { - if (action.id == "org.freedesktop.timedate1.set-timezone" - && subject.user == "localtimed") { - return polkit.Result.YES; - } - }); - ''; + # so polkit will pick up the rules + environment.systemPackages = [ pkgs.localtime ]; users.users = [{ name = "localtimed"; diff --git a/nixos/modules/services/web-apps/youtrack.nix b/nixos/modules/services/web-apps/youtrack.nix index 8c675c642005..6ad38028a641 100644 --- a/nixos/modules/services/web-apps/youtrack.nix +++ b/nixos/modules/services/web-apps/youtrack.nix @@ -118,14 +118,14 @@ in systemd.services.youtrack = { environment.HOME = cfg.statePath; - environment.YOUTRACK_JVM_OPTS = "-Xmx${cfg.maxMemory} -XX:MaxMetaspaceSize=${cfg.maxMetaspaceSize} ${cfg.jvmOpts} ${extraAttr}"; + environment.YOUTRACK_JVM_OPTS = "${extraAttr}"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; User = "youtrack"; Group = "youtrack"; - ExecStart = ''${cfg.package}/bin/youtrack ${cfg.address}:${toString cfg.port}''; + ExecStart = ''${cfg.package}/bin/youtrack --J-Xmx${cfg.maxMemory} --J-XX:MaxMetaspaceSize=${cfg.maxMetaspaceSize} ${cfg.jvmOpts} ${cfg.address}:${toString cfg.port}''; }; }; diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix index ee9b11928ae1..c339d24b098a 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome3.nix +++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix @@ -97,6 +97,8 @@ in { services.udisks2.enable = true; services.accounts-daemon.enable = true; services.geoclue2.enable = mkDefault true; + # GNOME should have its own geoclue agent + services.geoclue2.enableDemoAgent = false; services.dleyna-renderer.enable = mkDefault true; services.dleyna-server.enable = mkDefault true; services.gnome3.at-spi2-core.enable = true; diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix index 78095e7ce0b0..66886f23737d 100644 --- a/nixos/modules/services/x11/display-managers/default.nix +++ b/nixos/modules/services/x11/display-managers/default.nix @@ -56,10 +56,6 @@ let # Start PulseAudio if enabled. ${optionalString (config.hardware.pulseaudio.enable) '' - ${optionalString (!config.hardware.pulseaudio.systemWide) - "${config.hardware.pulseaudio.package.out}/bin/pulseaudio --start" - } - # Publish access credentials in the root window. if ${config.hardware.pulseaudio.package.out}/bin/pulseaudio --dump-modules | grep module-x11-publish &> /dev/null; then ${config.hardware.pulseaudio.package.out}/bin/pactl load-module module-x11-publish "display=$DISPLAY" diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix index 1b3478039327..2b03ed81b5ed 100644 --- a/nixos/modules/services/x11/display-managers/sddm.nix +++ b/nixos/modules/services/x11/display-managers/sddm.nix @@ -265,6 +265,7 @@ in }; environment.etc."sddm.conf".source = cfgFile; + environment.pathsToLink = [ "/share/sddm/themes" ]; users.groups.sddm.gid = config.ids.gids.sddm; diff --git a/nixos/modules/services/x11/redshift.nix b/nixos/modules/services/x11/redshift.nix index 30d853841ea4..b7dd7debcb63 100644 --- a/nixos/modules/services/x11/redshift.nix +++ b/nixos/modules/services/x11/redshift.nix @@ -116,6 +116,9 @@ in { } ]; + # needed so that .desktop files are installed, which geoclue cares about + environment.systemPackages = [ cfg.package ]; + services.geoclue2.enable = mkIf (cfg.provider == "geoclue2") true; systemd.user.services.redshift = diff --git a/nixos/modules/services/x11/window-managers/metacity.nix b/nixos/modules/services/x11/window-managers/metacity.nix index 436eccbaf0c2..5175fd7f3b1f 100644 --- a/nixos/modules/services/x11/window-managers/metacity.nix +++ b/nixos/modules/services/x11/window-managers/metacity.nix @@ -5,9 +5,7 @@ with lib; let cfg = config.services.xserver.windowManager.metacity; - xorg = config.services.xserver.package; - gnome = pkgs.gnome; - + inherit (pkgs) gnome3; in { @@ -20,16 +18,12 @@ in services.xserver.windowManager.session = singleton { name = "metacity"; start = '' - env LD_LIBRARY_PATH=${lib.makeLibraryPath [ xorg.libX11 xorg.libXext ]}:/usr/lib/ - # !!! Hack: load the schemas for Metacity. - GCONF_CONFIG_SOURCE=xml::~/.gconf ${gnome.GConf.out}/bin/gconftool-2 \ - --makefile-install-rule ${gnome.metacity}/etc/gconf/schemas/*.schemas # */ - ${gnome.metacity}/bin/metacity & + ${gnome3.metacity}/bin/metacity & waitPID=$! ''; }; - environment.systemPackages = [ gnome.metacity ]; + environment.systemPackages = [ gnome3.metacity ]; }; diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 1dc888c58227..6016a85ea061 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -42,7 +42,7 @@ def write_loader_conf(profile, generation): else: f.write("default nixos-generation-%d\n" % (generation)) if not @editor@: - f.write("editor 0"); + f.write("editor 0\n"); f.write("console-mode @consoleMode@\n"); os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf") diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 401c3cc69919..27c1f891f485 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -5,62 +5,171 @@ with lib; let luks = config.boot.initrd.luks; - openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name; '' + commonFunctions = '' + die() { + echo "$@" >&2 + exit 1 + } - # Wait for a target (e.g. device, keyFile, header, ...) to appear. wait_target() { local name="$1" local target="$2" + local secs="''${3:-10}" + local desc="''${4:-$name $target to appear}" if [ ! -e $target ]; then - echo -n "Waiting 10 seconds for $name $target to appear" + echo -n "Waiting $secs seconds for $desc..." local success=false; - for try in $(seq 10); do + for try in $(seq $secs); do echo -n "." sleep 1 - if [ -e $target ]; then success=true break; fi + if [ -e $target ]; then + success=true + break + fi done - if [ $success = true ]; then + if [ $success == true ]; then echo " - success"; + return 0 else echo " - failure"; + return 1 fi fi + return 0 + } + + wait_yubikey() { + local secs="''${1:-10}" + + ykinfo -v 1>/dev/null 2>&1 + if [ $? != 0 ]; then + echo -n "Waiting $secs seconds for Yubikey to appear..." + local success=false + for try in $(seq $secs); do + echo -n . + sleep 1 + ykinfo -v 1>/dev/null 2>&1 + if [ $? == 0 ]; then + success=true + break + fi + done + if [ $success == true ]; then + echo " - success"; + return 0 + else + echo " - failure"; + return 1 + fi + fi + return 0 } + ''; + + preCommands = '' + # A place to store crypto things + + # A ramfs is used here to ensure that the file used to update + # the key slot with cryptsetup will never get swapped out. + # Warning: Do NOT replace with tmpfs! + mkdir -p /crypt-ramfs + mount -t ramfs none /crypt-ramfs + + # For Yubikey salt storage + mkdir -p /crypt-storage + # Disable all input echo for the whole stage. We could use read -s + # instead but that would ocasionally leak characters between read + # invocations. + stty -echo + ''; + + postCommands = '' + stty echo + umount /crypt-storage 2>/dev/null + umount /crypt-ramfs 2>/dev/null + ''; + + openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name; + let + csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; + cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; + in '' # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g. # if on a USB drive. - wait_target "device" ${device} - - ${optionalString (keyFile != null) '' - wait_target "key file" ${keyFile} - ''} + wait_target "device" ${device} || die "${device} is unavailable" ${optionalString (header != null) '' - wait_target "header" ${header} + wait_target "header" ${header} || die "${header} is unavailable" ''} + do_open_passphrase() { + local passphrase + + while true; do + echo -n "Passphrase for ${device}: " + passphrase= + while true; do + if [ -e /crypt-ramfs/passphrase ]; then + echo "reused" + passphrase=$(cat /crypt-ramfs/passphrase) + break + else + # ask cryptsetup-askpass + echo -n "${device}" > /crypt-ramfs/device + + # and try reading it from /dev/console with a timeout + IFS= read -t 1 -r passphrase + if [ -n "$passphrase" ]; then + ${if luks.reusePassphrases then '' + # remember it for the next device + echo -n "$passphrase" > /crypt-ramfs/passphrase + '' else '' + # Don't save it to ramfs. We are very paranoid + ''} + echo + break + fi + fi + done + echo -n "Verifiying passphrase for ${device}..." + echo -n "$passphrase" | ${csopen} --key-file=- + if [ $? == 0 ]; then + echo " - success" + ${if luks.reusePassphrases then '' + # we don't rm here because we might reuse it for the next device + '' else '' + rm -f /crypt-ramfs/passphrase + ''} + break + else + echo " - failure" + # ask for a different one + rm -f /crypt-ramfs/passphrase + fi + done + } + + # LUKS open_normally() { - echo luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \ - ${optionalString (header != null) "--header=${header}"} \ - > /.luksopen_args - ${optionalString (keyFile != null) '' - ${optionalString fallbackToPassword "if [ -e ${keyFile} ]; then"} - echo " --key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}" \ - "${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"}" \ - >> /.luksopen_args - ${optionalString fallbackToPassword '' + ${if (keyFile != null) then '' + if wait_target "key file" ${keyFile}; then + ${csopen} --key-file=${keyFile} \ + ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \ + ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"} else - echo "keyfile ${keyFile} not found -- fallback to interactive unlocking" + ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable" + echo " - failing back to interactive password prompt" + do_open_passphrase fi + '' else '' + do_open_passphrase ''} - ''} - cryptsetup-askpass - rm /.luksopen_args } - ${optionalString (luks.yubikeySupport && (yubikey != null)) '' - + ${if luks.yubikeySupport && (yubikey != null) then '' + # Yubikey rbtohex() { ( od -An -vtx1 | tr -d ' \n' ) } @@ -69,8 +178,7 @@ let ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf ) } - open_yubikey() { - + do_open_yubikey() { # Make all of these local to this function # to prevent their values being leaked local salt @@ -86,19 +194,18 @@ let local new_response local new_k_luks - mkdir -p ${yubikey.storage.mountPoint} - mount -t ${yubikey.storage.fsType} ${toString yubikey.storage.device} ${yubikey.storage.mountPoint} + mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \ + die "Failed to mount Yubikey salt storage device" - salt="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 1p | tr -d '\n')" - iterations="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 2p | tr -d '\n')" + salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')" + iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')" challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)" response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" for try in $(seq 3); do - ${optionalString yubikey.twoFactor '' echo -n "Enter two-factor passphrase: " - read -s k_user + read -r k_user echo ''} @@ -108,9 +215,9 @@ let k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" fi - echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=- + echo -n "$k_luks" | hextorb | ${csopen} --key-file=- - if [ $? == "0" ]; then + if [ $? == 0 ]; then opened=true break else @@ -119,11 +226,7 @@ let fi done - if [ "$opened" == false ]; then - umount ${yubikey.storage.mountPoint} - echo "Maximum authentication errors reached" - exit 1 - fi + [ "$opened" == false ] && die "Maximum authentication errors reached" echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..." for i in $(seq ${toString yubikey.saltLength}); do @@ -148,67 +251,50 @@ let new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" fi - mkdir -p ${yubikey.ramfsMountPoint} - # A ramfs is used here to ensure that the file used to update - # the key slot with cryptsetup will never get swapped out. - # Warning: Do NOT replace with tmpfs! - mount -t ramfs none ${yubikey.ramfsMountPoint} - - echo -n "$new_k_luks" | hextorb > ${yubikey.ramfsMountPoint}/new_key - echo -n "$k_luks" | hextorb | cryptsetup luksChangeKey ${device} --key-file=- ${yubikey.ramfsMountPoint}/new_key + echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key + echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key - if [ $? == "0" ]; then - echo -ne "$new_salt\n$new_iterations" > ${yubikey.storage.mountPoint}${yubikey.storage.path} + if [ $? == 0 ]; then + echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path} else echo "Warning: Could not update LUKS key, current challenge persists!" fi - rm -f ${yubikey.ramfsMountPoint}/new_key - umount ${yubikey.ramfsMountPoint} - rm -rf ${yubikey.ramfsMountPoint} + rm -f /crypt-ramfs/new_key + umount /crypt-storage + } - umount ${yubikey.storage.mountPoint} + open_yubikey() { + if wait_yubikey ${toString yubikey.gracePeriod}; then + do_open_yubikey + else + echo "No yubikey found, falling back to non-yubikey open procedure" + open_normally + fi } - ${optionalString (yubikey.gracePeriod > 0) '' - echo -n "Waiting ${toString yubikey.gracePeriod} seconds as grace..." - for i in $(seq ${toString yubikey.gracePeriod}); do - sleep 1 - echo -n . - done - echo "ok" + open_yubikey + '' else '' + open_normally ''} + ''; - yubikey_missing=true - ykinfo -v 1>/dev/null 2>&1 - if [ $? != "0" ]; then - echo -n "waiting 10 seconds for yubikey to appear..." - for try in $(seq 10); do - sleep 1 - ykinfo -v 1>/dev/null 2>&1 - if [ $? == "0" ]; then - yubikey_missing=false - break - fi - echo -n . - done - echo "ok" - else - yubikey_missing=false - fi - - if [ "$yubikey_missing" == true ]; then - echo "no yubikey found, falling back to non-yubikey open procedure" - open_normally - else - open_yubikey - fi - ''} + askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' + #!/bin/sh - # open luksRoot and scan for logical volumes - ${optionalString ((!luks.yubikeySupport) || (yubikey == null)) '' - open_normally - ''} + ${commonFunctions} + + while true; do + wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now" + device=$(cat /crypt-ramfs/device) + + echo -n "Passphrase for $device: " + IFS= read -rs passphrase + echo + + rm /crypt-ramfs/device + echo -n "$passphrase" > /crypt-ramfs/passphrase + done ''; preLVM = filterAttrs (n: v: v.preLVM) luks.devices; @@ -256,6 +342,22 @@ in ''; }; + boot.initrd.luks.reusePassphrases = mkOption { + type = types.bool; + default = true; + description = '' + When opening a new LUKS device try reusing last successful + passphrase. + + Useful for mounting a number of devices that use the same + passphrase without retyping it several times. + + Such setup can be useful if you use <command>cryptsetup + luksSuspend</command>. Different LUKS devices will still have + different master keys even when using the same passphrase. + ''; + }; + boot.initrd.luks.devices = mkOption { default = { }; example = { "luksroot".device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; }; @@ -397,15 +499,9 @@ in }; gracePeriod = mkOption { - default = 2; + default = 10; type = types.int; - description = "Time in seconds to wait before attempting to find the Yubikey."; - }; - - ramfsMountPoint = mkOption { - default = "/crypt-ramfs"; - type = types.str; - description = "Path where the ramfs used to update the LUKS key will be mounted during early boot."; + description = "Time in seconds to wait for the Yubikey."; }; /* TODO: Add to the documentation of the current module: @@ -428,12 +524,6 @@ in description = "The filesystem of the unencrypted device."; }; - mountPoint = mkOption { - default = "/crypt-storage"; - type = types.str; - description = "Path where the unencrypted device will be mounted during early boot."; - }; - path = mkOption { default = "/crypt-storage/default"; type = types.str; @@ -446,8 +536,8 @@ in }; }); }; - - }; })); + }; + })); }; boot.initrd.luks.yubikeySupport = mkOption { @@ -477,18 +567,8 @@ in # copy the cryptsetup binary and it's dependencies boot.initrd.extraUtilsCommands = '' copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup - - cat > $out/bin/cryptsetup-askpass <<EOF - #!$out/bin/sh -e - if [ -e /.luksopen_args ]; then - cryptsetup \$(cat /.luksopen_args) - killall -q cryptsetup - else - echo "Passphrase is not requested now" - exit 1 - fi - EOF - chmod +x $out/bin/cryptsetup-askpass + copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass + sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass ${optionalString luks.yubikeySupport '' copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp @@ -520,8 +600,9 @@ in ''} ''; - boot.initrd.preLVMCommands = concatStrings (mapAttrsToList openCommand preLVM); - boot.initrd.postDeviceCommands = concatStrings (mapAttrsToList openCommand postLVM); + boot.initrd.preFailCommands = postCommands; + boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands; + boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands; environment.systemPackages = [ pkgs.cryptsetup ]; }; diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix index 71b806a0b4e1..2caab69cbb95 100644 --- a/nixos/modules/system/boot/stage-1.nix +++ b/nixos/modules/system/boot/stage-1.nix @@ -248,6 +248,14 @@ let isExecutable = true; + postInstall = '' + echo checking syntax + # check both with bash + ${pkgs.bash}/bin/sh -n $target + # and with ash shell, just in case + ${extraUtils}/bin/ash -n $target + ''; + inherit udevRules extraUtils modulesClosure; inherit (config.boot) resumeDevice; diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index 7120856387ef..2b3b09d725c7 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -23,12 +23,8 @@ let kernel = config.boot.kernelPackages; - packages = if config.boot.zfs.enableLegacyCrypto then { - spl = kernel.splLegacyCrypto; - zfs = kernel.zfsLegacyCrypto; - zfsUser = pkgs.zfsLegacyCrypto; - } else if config.boot.zfs.enableUnstable then { - spl = kernel.splUnstable; + packages = if config.boot.zfs.enableUnstable then { + spl = null; zfs = kernel.zfsUnstable; zfsUser = pkgs.zfsUnstable; } else { @@ -117,27 +113,6 @@ in ''; }; - enableLegacyCrypto = mkOption { - type = types.bool; - default = false; - description = '' - Enabling this option will allow you to continue to use the old format for - encrypted datasets. With the inclusion of stability patches the format of - encrypted datasets has changed. They can still be accessed and mounted but - in read-only mode mounted. It is highly recommended to convert them to - the new format. - - This option is only for convenience to people that cannot convert their - datasets to the new format yet and it will be removed in due time. - - For migration strategies from old format to this new one, check the Wiki: - https://nixos.wiki/wiki/NixOS_on_ZFS#Encrypted_Dataset_Format_Change - - See https://github.com/zfsonlinux/zfs/pull/6864 for more details about - the stability patches. - ''; - }; - extraPools = mkOption { type = types.listOf types.str; default = []; @@ -350,12 +325,12 @@ in virtualisation.lxd.zfsSupport = true; boot = { - kernelModules = [ "spl" "zfs" ] ; - extraModulePackages = with packages; [ spl zfs ]; + kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl"; + extraModulePackages = with packages; [ zfs ] ++ optional (!cfgZfs.enableUnstable) spl; }; boot.initrd = mkIf inInitrd { - kernelModules = [ "spl" "zfs" ]; + kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl"; extraUtilsCommands = '' copy_bin_and_libs ${packages.zfsUser}/sbin/zfs diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix index 3d002bc22329..3e38662f5b0f 100644 --- a/nixos/modules/virtualisation/libvirtd.nix +++ b/nixos/modules/virtualisation/libvirtd.nix @@ -17,6 +17,10 @@ let ${optionalString cfg.qemuOvmf '' nvram = ["/run/libvirt/nix-ovmf/OVMF_CODE.fd:/run/libvirt/nix-ovmf/OVMF_VARS.fd"] ''} + ${optionalString (!cfg.qemuRunAsRoot) '' + user = "qemu-libvirtd" + group = "qemu-libvirtd" + ''} ${cfg.qemuVerbatimConfig} ''; @@ -56,6 +60,18 @@ in { ''; }; + virtualisation.libvirtd.qemuRunAsRoot = mkOption { + type = types.bool; + default = true; + description = '' + If true, libvirtd runs qemu as root. + If false, libvirtd runs qemu as unprivileged user qemu-libvirtd. + Changing this option to false may cause file permission issues + for existing guests. To fix these, manually change ownership + of affected files in /var/lib/libvirt/qemu to qemu-libvirtd. + ''; + }; + virtualisation.libvirtd.qemuVerbatimConfig = mkOption { type = types.lines; default = '' @@ -110,6 +126,14 @@ in { users.groups.libvirtd.gid = config.ids.gids.libvirtd; + # libvirtd runs qemu as this user and group by default + users.extraGroups.qemu-libvirtd.gid = config.ids.gids.qemu-libvirtd; + users.extraUsers.qemu-libvirtd = { + uid = config.ids.uids.qemu-libvirtd; + isNormalUser = false; + group = "qemu-libvirtd"; + }; + systemd.packages = [ pkgs.libvirt ]; systemd.services.libvirtd = { diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix index af0a27b0ad86..60779579402c 100644 --- a/nixos/modules/virtualisation/virtualbox-host.nix +++ b/nixos/modules/virtualisation/virtualbox-host.nix @@ -5,7 +5,7 @@ with lib; let cfg = config.virtualisation.virtualbox.host; - virtualbox = pkgs.virtualbox.override { + virtualbox = cfg.package.override { inherit (cfg) enableHardening headless; extensionPack = if cfg.enableExtensionPack then pkgs.virtualboxExtpack else null; }; @@ -40,6 +40,15 @@ in ''; }; + package = mkOption { + type = types.package; + default = pkgs.virtualbox; + defaultText = "pkgs.virtualbox"; + description = '' + Which VirtualBox package to use. + ''; + }; + addNetworkInterface = mkOption { type = types.bool; default = true; diff --git a/nixos/release.nix b/nixos/release.nix index 649517130e0e..1d1f8009f27c 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -256,6 +256,7 @@ in rec { tests.buildbot = callTest tests/buildbot.nix {}; tests.cadvisor = callTestOnMatchingSystems ["x86_64-linux"] tests/cadvisor.nix {}; tests.ceph = callTestOnMatchingSystems ["x86_64-linux"] tests/ceph.nix {}; + tests.certmgr = callSubTests tests/certmgr.nix {}; tests.cfssl = callTestOnMatchingSystems ["x86_64-linux"] tests/cfssl.nix {}; tests.chromium = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/chromium.nix {}).stable or {}; tests.cjdns = callTest tests/cjdns.nix {}; diff --git a/nixos/tests/certmgr.nix b/nixos/tests/certmgr.nix new file mode 100644 index 000000000000..8354c46b85f7 --- /dev/null +++ b/nixos/tests/certmgr.nix @@ -0,0 +1,148 @@ +{ system ? builtins.currentSystem }: + +with import ../lib/testing.nix { inherit system; }; +let + mkSpec = { host, service ? null, action }: { + inherit action; + authority = { + file = { + group = "nobody"; + owner = "nobody"; + path = "/tmp/${host}-ca.pem"; + }; + label = "www_ca"; + profile = "three-month"; + remote = "localhost:8888"; + }; + certificate = { + group = "nobody"; + owner = "nobody"; + path = "/tmp/${host}-cert.pem"; + }; + private_key = { + group = "nobody"; + mode = "0600"; + owner = "nobody"; + path = "/tmp/${host}-key.pem"; + }; + request = { + CN = host; + hosts = [ host "www.${host}" ]; + key = { + algo = "rsa"; + size = 2048; + }; + names = [ + { + C = "US"; + L = "San Francisco"; + O = "Example, LLC"; + ST = "CA"; + } + ]; + }; + inherit service; + }; + + mkCertmgrTest = { svcManager, specs, testScript }: makeTest { + name = "certmgr-" + svcManager; + nodes = { + machine = { config, lib, pkgs, ... }: { + networking.firewall.allowedTCPPorts = with config.services; [ cfssl.port certmgr.metricsPort ]; + networking.extraHosts = "127.0.0.1 imp.example.org decl.example.org"; + + services.cfssl.enable = true; + systemd.services.cfssl.after = [ "cfssl-init.service" "networking.target" ]; + + systemd.services.cfssl-init = { + description = "Initialize the cfssl CA"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = "cfssl"; + Type = "oneshot"; + WorkingDirectory = config.services.cfssl.dataDir; + }; + script = '' + ${pkgs.cfssl}/bin/cfssl genkey -initca ${pkgs.writeText "ca.json" (builtins.toJSON { + hosts = [ "ca.example.com" ]; + key = { + algo = "rsa"; size = 4096; }; + names = [ + { + C = "US"; + L = "San Francisco"; + O = "Internet Widgets, LLC"; + OU = "Certificate Authority"; + ST = "California"; + } + ]; + })} | ${pkgs.cfssl}/bin/cfssljson -bare ca + ''; + }; + + services.nginx = { + enable = true; + virtualHosts = lib.mkMerge (map (host: { + ${host} = { + sslCertificate = "/tmp/${host}-cert.pem"; + sslCertificateKey = "/tmp/${host}-key.pem"; + extraConfig = '' + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ''; + onlySSL = true; + serverName = host; + root = pkgs.writeTextDir "index.html" "It works!"; + }; + }) [ "imp.example.org" "decl.example.org" ]); + }; + + systemd.services.nginx.wantedBy = lib.mkForce []; + + systemd.services.certmgr.after = [ "cfssl.service" ]; + services.certmgr = { + enable = true; + inherit svcManager; + inherit specs; + }; + + }; + }; + inherit testScript; + }; +in +{ + systemd = mkCertmgrTest { + svcManager = "systemd"; + specs = { + decl = mkSpec { host = "decl.example.org"; service = "nginx"; action ="restart"; }; + imp = toString (pkgs.writeText "test.json" (builtins.toJSON ( + mkSpec { host = "imp.example.org"; service = "nginx"; action = "restart"; } + ))); + }; + testScript = '' + $machine->waitForUnit('cfssl.service'); + $machine->waitUntilSucceeds('ls /tmp/decl.example.org-ca.pem'); + $machine->waitUntilSucceeds('ls /tmp/decl.example.org-key.pem'); + $machine->waitUntilSucceeds('ls /tmp/decl.example.org-cert.pem'); + $machine->waitUntilSucceeds('ls /tmp/imp.example.org-ca.pem'); + $machine->waitUntilSucceeds('ls /tmp/imp.example.org-key.pem'); + $machine->waitUntilSucceeds('ls /tmp/imp.example.org-cert.pem'); + $machine->waitForUnit('nginx.service'); + $machine->succeed('[ "1" -lt "$(journalctl -u nginx | grep "Starting Nginx" | wc -l)" ]'); + $machine->succeed('curl --cacert /tmp/imp.example.org-ca.pem https://imp.example.org'); + $machine->succeed('curl --cacert /tmp/decl.example.org-ca.pem https://decl.example.org'); + ''; + }; + + command = mkCertmgrTest { + svcManager = "command"; + specs = { + test = mkSpec { host = "command.example.org"; action = "touch /tmp/command.executed"; }; + }; + testScript = '' + $machine->waitForUnit('cfssl.service'); + $machine->waitUntilSucceeds('stat /tmp/command.executed'); + ''; + }; + +} diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix index 98d99811f3c0..db4e97e0039b 100644 --- a/nixos/tests/hydra/default.nix +++ b/nixos/tests/hydra/default.nix @@ -2,14 +2,11 @@ import ../make-test.nix ({ pkgs, ...} : let trivialJob = pkgs.writeTextDir "trivial.nix" '' - with import <nix/config.nix>; - { trivial = builtins.derivation { name = "trivial"; system = "x86_64-linux"; - PATH = coreutils; - builder = shell; - args = ["-c" "touch $out; exit 0"]; + builder = "/bin/sh"; + args = ["-c" "echo success > $out; exit 0"]; }; } ''; @@ -27,7 +24,7 @@ let in { name = "hydra-init-localdb"; meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ pstn lewo ]; + maintainers = [ pstn lewo ma27 ]; }; machine = @@ -50,6 +47,8 @@ in { hostName = "localhost"; systems = [ "x86_64-linux" ]; }]; + + binaryCaches = []; }; }; @@ -74,5 +73,5 @@ in { $machine->succeed("create-trivial-project.sh"); $machine->waitUntilSucceeds('curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" | jq .buildstatus | xargs test 0 -eq'); - ''; + ''; }) diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index 2455b9152bd9..507665190a26 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -467,7 +467,7 @@ in { enableOCR = true; preBootCommands = '' $machine->start; - $machine->waitForText(qr/Enter passphrase/); + $machine->waitForText(qr/Passphrase for/); $machine->sendChars("supersecret\n"); ''; }; diff --git a/nixos/tests/yabar.nix b/nixos/tests/yabar.nix index 40ca91e8064d..06fe5bc2b278 100644 --- a/nixos/tests/yabar.nix +++ b/nixos/tests/yabar.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, lib }: +import ./make-test.nix ({ pkgs, lib, ... }: with lib; |