summary refs log tree commit diff
path: root/nixos/modules/system/boot
diff options
context:
space:
mode:
authorFrederik Rietdijk <fridh@fridh.nl>2018-08-09 18:28:15 +0200
committerFrederik Rietdijk <fridh@fridh.nl>2018-08-09 18:28:15 +0200
commitd9fa74ba7891cfae4a029ee79dd29e4ab3425385 (patch)
tree82f79c8dc3d5cf7108055155e6332f64485b0acf /nixos/modules/system/boot
parent8b834605628fabd43b1b26a1724fb22c83150e2d (diff)
parent8c7fa1e836b8929d29c7ec34f968a3eef11e69de (diff)
downloadnixlib-d9fa74ba7891cfae4a029ee79dd29e4ab3425385.tar
nixlib-d9fa74ba7891cfae4a029ee79dd29e4ab3425385.tar.gz
nixlib-d9fa74ba7891cfae4a029ee79dd29e4ab3425385.tar.bz2
nixlib-d9fa74ba7891cfae4a029ee79dd29e4ab3425385.tar.lz
nixlib-d9fa74ba7891cfae4a029ee79dd29e4ab3425385.tar.xz
nixlib-d9fa74ba7891cfae4a029ee79dd29e4ab3425385.tar.zst
nixlib-d9fa74ba7891cfae4a029ee79dd29e4ab3425385.zip
Merge master into staging
Diffstat (limited to 'nixos/modules/system/boot')
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py1
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix34
-rw-r--r--nixos/modules/system/boot/luksroot.nix337
-rw-r--r--nixos/modules/system/boot/stage-1.nix8
-rw-r--r--nixos/modules/system/boot/systemd.nix5
5 files changed, 261 insertions, 124 deletions
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 3333569c36be..1dc888c58227 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
@@ -43,6 +43,7 @@ def write_loader_conf(profile, generation):
             f.write("default nixos-generation-%d\n" % (generation))
         if not @editor@:
             f.write("editor 0");
+        f.write("console-mode @consoleMode@\n");
     os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
 
 def profile_path(profile, generation, name):
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index a5a88a99be8f..feed863efd66 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -22,6 +22,8 @@ let
 
     editor = if cfg.editor then "True" else "False";
 
+    inherit (cfg) consoleMode;
+
     inherit (efi) efiSysMountPoint canTouchEfiVariables;
   };
 in {
@@ -52,6 +54,38 @@ in {
         compatibility.
       '';
     };
+
+    consoleMode = mkOption {
+      default = "keep";
+
+      type = types.enum [ "0" "1" "2" "auto" "max" "keep" ];
+
+      description = ''
+        The resolution of the console. The following values are valid:
+        </para>
+        <para>
+        <itemizedlist>
+          <listitem><para>
+            <literal>"0"</literal>: Standard UEFI 80x25 mode
+          </para></listitem>
+          <listitem><para>
+            <literal>"1"</literal>: 80x50 mode, not supported by all devices
+          </para></listitem>
+          <listitem><para>
+            <literal>"2"</literal>: The first non-standard mode provided by the device firmware, if any
+          </para></listitem>
+          <listitem><para>
+            <literal>"auto"</literal>: Pick a suitable mode automatically using heuristics
+          </para></listitem>
+          <listitem><para>
+            <literal>"max"</literal>: Pick the highest-numbered available mode
+          </para></listitem>
+          <listitem><para>
+            <literal>"keep"</literal>: Keep the mode selected by firmware (the default)
+          </para></listitem>
+        </itemizedlist>
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 7ebfdb134d7d..27c1f891f485 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -5,61 +5,171 @@ with lib;
 let
   luks = config.boot.initrd.luks;
 
-  openCommand = name': { name, device, header, keyFile, keyFileSize, 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}"}" \
-              >> /.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' )
     }
@@ -68,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
@@ -85,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
             ''}
 
@@ -107,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
@@ -118,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
@@ -147,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 > /crypt-ramfs/new_key
+        echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
 
-        echo -n "$new_k_luks" | hextorb > ${yubikey.ramfsMountPoint}/new_key
-        echo -n "$k_luks" | hextorb | cryptsetup luksChangeKey ${device} --key-file=- ${yubikey.ramfsMountPoint}/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;
@@ -255,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"; };
@@ -316,6 +419,19 @@ in
             '';
           };
 
+          keyFileOffset = mkOption {
+            default = null;
+            example = 4096;
+            type = types.nullOr types.int;
+            description = ''
+              The offset of the key file. Use this in combination with
+              <literal>keyFileSize</literal> to use part of a file as key file
+              (often the case if a raw device or partition is used as a key file).
+              If not specified, the key begins at the first byte of
+              <literal>keyFile</literal>.
+            '';
+          };
+
           # FIXME: get rid of this option.
           preLVM = mkOption {
             default = true;
@@ -383,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:
@@ -414,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;
@@ -432,8 +536,8 @@ in
               };
             });
           };
-
-        }; }));
+        };
+      }));
     };
 
     boot.initrd.luks.yubikeySupport = mkOption {
@@ -463,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
@@ -506,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 35062d891b8b..f58b68cb3353 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/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 737d1c10db87..12e029ae57f8 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -190,9 +190,8 @@ let
     ];
 
   makeJobScript = name: text:
-    let mkScriptName =  s: (replaceChars [ "\\" ] [ "-" ] (shellEscape s) );
-        x = pkgs.writeTextFile { name = "unit-script"; executable = true; destination = "/bin/${mkScriptName name}"; inherit text; };
-    in "${x}/bin/${mkScriptName name}";
+    let mkScriptName =  s: "unit-script-" + (replaceChars [ "\\" "@" ] [ "-" "_" ] (shellEscape s) );
+    in  pkgs.writeTextFile { name = mkScriptName name; executable = true; inherit text; };
 
   unitConfig = { config, ... }: {
     config = {