Skip to content

Commit 48f06de

Browse files
committed
make installer with sudo-only access work and re-enable ssh-ng
Now that our nixos installers should have a fixed ssh-ng implementation, it should be safe to re-enable it. On top we now test the use-case of an installer that doesn't need kexec but sudo explicitly.
1 parent 31e9ff1 commit 48f06de

File tree

4 files changed

+189
-69
lines changed

4 files changed

+189
-69
lines changed

src/nixos-anywhere.sh

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ mkdir -p "$tempDir"
6464

6565
declare -A diskEncryptionKeys=()
6666
declare -A extraFilesOwnership=()
67-
declare -a nixCopyOptions=()
67+
declare -a nixCopyOptions=(--no-check-sigs)
6868
declare -a sshArgs=("-o" "IdentitiesOnly=yes" "-i" "$tempDir/nixos-anywhere" "-o" "UserKnownHostsFile=/dev/null" "-o" "StrictHostKeyChecking=no")
6969

7070
showUsage() {
@@ -420,19 +420,76 @@ runSsh() {
420420
ssh "$sshTtyParam" "${sshArgs[@]}" "$sshConnection" "$@"
421421
}
422422

423+
buildStoreUrl() {
424+
local storeUrl="$1"
425+
426+
# Add sshStoreSettings if present
427+
if [[ -n ${sshStoreSettings} ]] && [[ $storeUrl == ssh-ng://* ]]; then
428+
if [[ $storeUrl == *"?"* ]]; then
429+
storeUrl="${storeUrl}&${sshStoreSettings}"
430+
else
431+
storeUrl="${storeUrl}?${sshStoreSettings}"
432+
fi
433+
fi
434+
435+
# Add remote-program parameter when sudo is needed
436+
if [[ -n ${maybeSudo} ]] && [[ $storeUrl == ssh-ng://* ]]; then
437+
if [[ $storeUrl == *"?"* ]]; then
438+
storeUrl="${storeUrl}&remote-program=${maybeSudo} nix-daemon"
439+
else
440+
storeUrl="${storeUrl}?remote-program=${maybeSudo} nix-daemon"
441+
fi
442+
fi
443+
444+
echo "$storeUrl"
445+
}
446+
423447
nixCopy() {
424-
NIX_SSHOPTS="${sshArgs[*]}" nix copy \
425-
"${nixOptions[@]}" \
426-
"${nixCopyOptions[@]}" \
427-
"$@"
448+
# Process arguments to add remote-program parameter when sudo is needed
449+
local processedArgs=()
450+
local i=1
451+
while [[ $i -le $# ]]; do
452+
local arg="${!i}"
453+
if [[ $arg == "--to" ]]; then
454+
processedArgs+=("$arg")
455+
((i++))
456+
local storeUrl="${!i}"
457+
storeUrl=$(buildStoreUrl "$storeUrl")
458+
processedArgs+=("$storeUrl")
459+
else
460+
processedArgs+=("$arg")
461+
fi
462+
((i++))
463+
done
464+
465+
local nixCopyArgs=("${nixOptions[@]}" "${nixCopyOptions[@]}")
466+
467+
NIX_SSHOPTS="${sshArgs[*]}" nix copy "${nixCopyArgs[@]}" "${processedArgs[@]}"
428468
}
429469
nixBuild() {
470+
# Process arguments to add remote-program parameter when sudo is needed
471+
local processedArgs=()
472+
local i=1
473+
while [[ $i -le $# ]]; do
474+
local arg="${!i}"
475+
if [[ $arg == "--store" ]]; then
476+
processedArgs+=("$arg")
477+
((i++))
478+
local storeUrl="${!i}"
479+
storeUrl=$(buildStoreUrl "$storeUrl")
480+
processedArgs+=("$storeUrl")
481+
else
482+
processedArgs+=("$arg")
483+
fi
484+
((i++))
485+
done
486+
430487
NIX_SSHOPTS="${sshArgs[*]}" nix build \
431488
--print-out-paths \
432489
--no-link \
433490
"${nixBuildFlags[@]}" \
434491
"${nixOptions[@]}" \
435-
"$@"
492+
"${processedArgs[@]}"
436493
}
437494

438495
runVmTest() {
@@ -688,6 +745,8 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr
688745
689746
# After kexec we explicitly set the user to root@
690747
sshConnection="root@${sshHost}"
748+
# After kexec, we're running as root in the NixOS installer, so no need for sudo
749+
maybeSudo=""
691750
692751
# waiting for machine to become available again
693752
until runSsh -o ConnectTimeout=10 -- exit 0; do sleep 5; done
@@ -697,55 +756,54 @@ runDisko() {
697756
local diskoScript=$1
698757
for path in "${!diskEncryptionKeys[@]}"; do
699758
step "Uploading ${diskEncryptionKeys[$path]} to $path"
700-
runSsh "umask 077; mkdir -p \"$(dirname "$path")\"; cat > $path" <"${diskEncryptionKeys[$path]}"
759+
runSsh "${maybeSudo} sh -c $(printf '%q' "umask 077; mkdir -p $(dirname "$path"); cat > $path")" <"${diskEncryptionKeys[$path]}"
701760
done
702761
if [[ -n ${diskoScript} ]]; then
703-
nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "$diskoScript"
762+
nixCopy --to "ssh-ng://$sshConnection" "$diskoScript"
704763
elif [[ ${buildOn} == "remote" ]]; then
705764
step Building disko script
706765
# We need to do a nix copy first because nix build doesn't have --no-check-sigs
707766
# Use ssh:// here to avoid https://github.com/NixOS/nix/issues/7359
708-
nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "${flake}#${flakeAttr}.system.build.${diskoMode}Script" \
709-
--derivation --no-check-sigs
767+
nixCopy --to "ssh-ng://$sshConnection" --derivation "${flake}#${flakeAttr}.system.build.${diskoMode}Script"
710768
# If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store`
711769
diskoScript=$(
712770
nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}" \
713-
--eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere&$sshStoreSettings"
771+
--eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere"
714772
)
715773
fi
716774
717775
step Formatting hard drive with disko
718-
runSsh "$diskoScript"
776+
runSsh "${maybeSudo} $diskoScript"
719777
}
720778
721779
nixosInstall() {
722780
local nixosSystem=$1
781+
local remoteStoreUrl="remote-store=local%3Froot=%2Fmnt"
723782
if [[ -n ${nixosSystem} ]]; then
724783
step Uploading the system closure
725-
nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "$nixosSystem"
784+
nixCopy --to "ssh-ng://$sshConnection?${remoteStoreUrl}" "$nixosSystem"
726785
elif [[ ${buildOn} == "remote" ]]; then
727786
step Building the system closure
728787
# We need to do a nix copy first because nix build doesn't have --no-check-sigs
729788
# Use ssh:// here to avoid https://github.com/NixOS/nix/issues/7359
730-
nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "${flake}#${flakeAttr}.system.build.toplevel" \
731-
--derivation --no-check-sigs
789+
nixCopy --to "ssh-ng://$sshConnection?${remoteStoreUrl}" --derivation "${flake}#${flakeAttr}.system.build.toplevel"
732790
# If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store`
733791
nixosSystem=$(
734792
nixBuild "${flake}#${flakeAttr}.system.build.toplevel" \
735-
--eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere&remote-store=local%3Froot=%2Fmnt&$sshStoreSettings"
793+
--eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere&${remoteStoreUrl}"
736794
)
737795
fi
738796
739797
if [[ -n ${extraFiles} ]]; then
740798
step Copying extra files
741-
tar -C "$extraFiles" -cpf- . | runSsh "tar -C /mnt -xf- --no-same-owner"
799+
tar -C "$extraFiles" -cpf- . | runSsh "${maybeSudo} tar -C /mnt -xf- --no-same-owner"
742800
743-
runSsh "chmod 755 /mnt" # tar also changes permissions of /mnt
801+
runSsh "${maybeSudo} chmod 755 /mnt" # tar also changes permissions of /mnt
744802
fi
745803
746804
if [[ ${#extraFilesOwnership[@]} -gt 0 ]]; then
747805
# shellcheck disable=SC2016
748-
printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do chown -R "$ownership" "/mnt/$file"; done'
806+
printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do '"${maybeSudo}"' chown -R "$ownership" "/mnt/$file"; done'
749807
fi
750808
751809
step Installing NixOS
@@ -756,27 +814,27 @@ export PATH="\$PATH:/run/current-system/sw/bin"
756814
757815
if [ ! -d "/mnt/tmp" ]; then
758816
# needed for installation if initrd-secrets are used
759-
mkdir -p /mnt/tmp
760-
chmod 777 /mnt/tmp
817+
${maybeSudo} mkdir -p /mnt/tmp
818+
${maybeSudo} chmod 777 /mnt/tmp
761819
fi
762820
763821
if [ ${copyHostKeys-n} = "y" ]; then
764822
# NB we copy host keys that are in turn copied by kexec installer.
765-
mkdir -m 755 -p /mnt/etc/ssh
823+
${maybeSudo} mkdir -m 755 -p /mnt/etc/ssh
766824
for p in /etc/ssh/ssh_host_*; do
767825
# Skip if the source file does not exist (i.e. glob did not match any files)
768826
# or the destination already exists (e.g. copied with --extra-files).
769827
if [ ! -e "\$p" ] || [ -e "/mnt/\$p" ]; then
770828
continue
771829
fi
772-
cp -a "\$p" "/mnt/\$p"
830+
${maybeSudo} cp -a "\$p" "/mnt/\$p"
773831
done
774832
fi
775833
# https://stackoverflow.com/a/13864829
776834
if [ ! -z ${NIXOS_NO_CHECK+0} ]; then
777835
export NIXOS_NO_CHECK
778836
fi
779-
nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem"
837+
${maybeSudo} nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem"
780838
SSH
781839
782840
}
@@ -786,11 +844,11 @@ nixosReboot() {
786844
runSsh sh <<SSH
787845
if command -v zpool >/dev/null && [ "\$(zpool list)" != "no pools available" ]; then
788846
# we always want to export the zfs pools so people can boot from it without force import
789-
umount -Rv /mnt/
790-
swapoff -a
791-
zpool export -a || true
847+
${maybeSudo} umount -Rv /mnt/
848+
${maybeSudo} swapoff -a
849+
${maybeSudo} zpool export -a || true
792850
fi
793-
nohup sh -c 'sleep 6 && reboot' >/dev/null &
851+
${maybeSudo} nohup sh -c 'sleep 6 && reboot' >/dev/null &
794852
SSH
795853
796854
step Waiting for the machine to become unreachable due to reboot
@@ -840,7 +898,6 @@ main() {
840898
fi
841899
842900
sshSettings=$(ssh "${sshArgs[@]}" -G "${sshConnection}")
843-
sshUser=$(echo "$sshSettings" | awk '/^user / { print $2 }')
844901
sshHost=$(echo "$sshSettings" | awk '/^hostname / { print $2 }')
845902
846903
uploadSshKey
@@ -898,14 +955,6 @@ main() {
898955
fi
899956
fi
900957
901-
# Installation will fail if non-root user is used for installer.
902-
# Switch to root user by copying authorized_keys.
903-
if [[ ${isInstaller} == "y" ]] && [[ ${sshUser} != "root" ]]; then
904-
# Allow copy to fail if authorized_keys does not exist, like if using /etc/ssh/authorized_keys.d/
905-
runSsh "${maybeSudo} mkdir -p /root/.ssh; ${maybeSudo} cp ~/.ssh/authorized_keys /root/.ssh || true"
906-
sshConnection="root@${sshHost}"
907-
fi
908-
909958
if [[ ${phases[disko]} == 1 ]]; then
910959
runDisko "$diskoScript"
911960
fi

tests/flake-module.nix

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
./modules/system-to-install.nix
88
inputs.disko.nixosModules.disko
99
];
10+
system-to-install-vdb = pkgs.nixos [
11+
./modules/system-to-install.nix
12+
inputs.disko.nixosModules.disko
13+
{
14+
nixos-anywhere.diskDevice = "/dev/vdb";
15+
}
16+
];
1017
testInputsUnstable = {
1118
inherit pkgs;
1219
inherit (inputs.disko.nixosModules) disko;
@@ -17,6 +24,9 @@
1724
testInputsStable = testInputsUnstable // {
1825
kexec-installer = "${inputs'.nixos-images.packages.kexec-installer-nixos-stable-noninteractive}/nixos-kexec-installer-noninteractive-${system}.tar.gz";
1926
};
27+
testInputsInstallerSudo = testInputsUnstable // {
28+
system-to-install = system-to-install-vdb;
29+
};
2030
linuxTestInputs = testInputsUnstable // {
2131
nix-vm-test = inputs.nix-vm-test;
2232
};
@@ -26,6 +36,7 @@
2636
from-nixos-stable = import ./from-nixos.nix testInputsStable;
2737
from-nixos-with-sudo = import ./from-nixos-with-sudo.nix testInputsUnstable;
2838
from-nixos-with-sudo-stable = import ./from-nixos-with-sudo.nix testInputsStable;
39+
from-nixos-installer-with-sudo = import ./from-nixos-installer-with-sudo.nix testInputsInstallerSudo;
2940
from-nixos-with-generated-config = import ./from-nixos-generate-config.nix testInputsUnstable;
3041
from-nixos-build-on-remote = import ./from-nixos-build-on-remote.nix testInputsUnstable;
3142
from-nixos-separated-phases = import ./from-nixos-separated-phases.nix testInputsUnstable;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
(import ./lib/test-base.nix) {
2+
name = "from-nixos-installer-with-sudo";
3+
nodes = {
4+
installer = ./modules/installer.nix;
5+
installed = { modulesPath, ... }: {
6+
imports = [
7+
(modulesPath + "/installer/cd-dvd/installation-cd-base.nix")
8+
];
9+
10+
services.openssh.enable = true;
11+
virtualisation.memorySize = 1500;
12+
virtualisation.emptyDiskImages = [ 1024 ];
13+
14+
users.users.nixos = {
15+
isNormalUser = true;
16+
openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ];
17+
extraGroups = [ "wheel" ];
18+
};
19+
security.sudo.enable = true;
20+
security.sudo.wheelNeedsPassword = false;
21+
22+
# Configure nix trusted users for remote builds with sudo
23+
nix.settings.trusted-users = [ "root" "nixos" ];
24+
};
25+
};
26+
testScript = ''
27+
start_all()
28+
installer.succeed("echo super-secret > /tmp/disk-1.key")
29+
installer.succeed("mkdir -p /tmp/extra-files/var/lib/secrets")
30+
installer.succeed("echo test-value > /tmp/extra-files/var/lib/secrets/test")
31+
32+
output = installer.succeed("""
33+
nixos-anywhere \
34+
-i /root/.ssh/install_key \
35+
--debug \
36+
--phases disko,install \
37+
--disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \
38+
--extra-files /tmp/extra-files \
39+
--store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \
40+
nixos@installed >&2
41+
echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
42+
nixos@installed sudo cat /tmp/disk-1.key)'"
43+
echo "extra-file: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
44+
nixos@installed sudo cat /mnt/var/lib/secrets/test)'"
45+
""")
46+
47+
assert "disk-1.key: 'super-secret'" in output, f"output does not contain expected values: {output}"
48+
assert "extra-file: 'test-value'" in output, f"output does not contain expected values: {output}"
49+
'';
50+
}

0 commit comments

Comments
 (0)