staging-nixos merge of 2026-06-27 (#535985)
This commit is contained in:
@@ -18,8 +18,6 @@
|
||||
netpbm,
|
||||
vhost-device-vsock,
|
||||
nixosTests,
|
||||
qemu_pkg ? qemu_test,
|
||||
qemu_test,
|
||||
setuptools,
|
||||
socat,
|
||||
systemd,
|
||||
@@ -56,7 +54,6 @@ buildPythonApplication {
|
||||
propagatedBuildInputs = [
|
||||
coreutils
|
||||
netpbm
|
||||
qemu_pkg
|
||||
socat
|
||||
util-linux
|
||||
vde2
|
||||
|
||||
@@ -1688,6 +1688,7 @@ class NspawnMachine(BaseMachine):
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
[self.start_command],
|
||||
cwd=self.state_dir,
|
||||
env={
|
||||
"RUN_NSPAWN_ROOT_DIR": str(self.state_dir),
|
||||
"RUN_NSPAWN_SHARED_DIR": str(self.shared_dir),
|
||||
|
||||
@@ -20,7 +20,6 @@ let
|
||||
# the respective qemu version and with or without ocr support
|
||||
testDriver = config.pythonTestDriverPackage.override {
|
||||
inherit (config) enableOCR extraPythonPackages;
|
||||
qemu_pkg = config.qemu.package;
|
||||
enableNspawn = config.containers != { };
|
||||
};
|
||||
|
||||
|
||||
@@ -461,10 +461,17 @@ in
|
||||
"${config.boot.initrd.systemd.package}/lib/systemd/systemd-udevd"
|
||||
"${config.boot.initrd.systemd.package}/lib/udev/ata_id"
|
||||
"${config.boot.initrd.systemd.package}/lib/udev/cdrom_id"
|
||||
"${config.boot.initrd.systemd.package}/lib/udev/dmi_memory_id"
|
||||
"${config.boot.initrd.systemd.package}/lib/udev/scsi_id"
|
||||
"${config.boot.initrd.systemd.package}/lib/udev/rules.d"
|
||||
]
|
||||
++ lib.optional (
|
||||
# https://github.com/systemd/systemd/blob/v259/meson.build#L1529-L1530
|
||||
pkgs.stdenv.hostPlatform.isx86
|
||||
|| pkgs.stdenv.hostPlatform.isAarch
|
||||
|| pkgs.stdenv.hostPlatform.isLoongArch64
|
||||
|| pkgs.stdenv.hostPlatform.isMips
|
||||
|| pkgs.stdenv.hostPlatform.isRiscV64
|
||||
) "${config.boot.initrd.systemd.package}/lib/udev/dmi_memory_id"
|
||||
++ map (x: "${x}/bin") config.boot.initrd.services.udev.binPackages;
|
||||
|
||||
# Generate the udev rules for the initrd
|
||||
|
||||
@@ -4231,8 +4231,10 @@ let
|
||||
"systemd-networkd-wait-online@.service"
|
||||
"systemd-networkd.service"
|
||||
"systemd-networkd.socket"
|
||||
"systemd-networkd-persistent-storage.service"
|
||||
"systemd-networkd-resolve-hook.socket"
|
||||
"systemd-networkd-varlink-metrics.socket"
|
||||
"systemd-networkd-varlink.socket"
|
||||
"systemd-networkd-persistent-storage.service"
|
||||
];
|
||||
|
||||
systemd.sockets.systemd-networkd-varlink-metrics.wantedBy = [ "sockets.target" ];
|
||||
@@ -4314,6 +4316,9 @@ let
|
||||
systemd.additionalUpstreamUnits = [
|
||||
"systemd-networkd-wait-online.service"
|
||||
"systemd-networkd.service"
|
||||
"systemd-networkd-resolve-hook.socket"
|
||||
"systemd-networkd-varlink-metrics.socket"
|
||||
"systemd-networkd-varlink.socket"
|
||||
"systemd-networkd.socket"
|
||||
"systemd-network-generator.service"
|
||||
"network-online.target"
|
||||
|
||||
@@ -185,7 +185,11 @@ in
|
||||
# added with order 501 to allow modules to go before with mkBefore
|
||||
system.nssDatabases.hosts = (mkOrder 501 [ "resolve [!UNAVAIL=return]" ]);
|
||||
|
||||
systemd.additionalUpstreamSystemUnits = [ "systemd-resolved.service" ];
|
||||
systemd.additionalUpstreamSystemUnits = [
|
||||
"systemd-resolved.service"
|
||||
"systemd-resolved-monitor.socket"
|
||||
"systemd-resolved-varlink.socket"
|
||||
];
|
||||
|
||||
systemd.services.systemd-resolved = {
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
@@ -248,7 +252,12 @@ in
|
||||
tmpfiles.settings.systemd-resolved-stub."/etc/resolv.conf".L.argument =
|
||||
"/run/systemd/resolve/stub-resolv.conf";
|
||||
|
||||
additionalUpstreamUnits = [ "systemd-resolved.service" ];
|
||||
additionalUpstreamUnits = [
|
||||
"systemd-resolved.service"
|
||||
"systemd-resolved-monitor.socket"
|
||||
"systemd-resolved-varlink.socket"
|
||||
];
|
||||
|
||||
users.systemd-resolve = { };
|
||||
groups.systemd-resolve = { };
|
||||
storePaths = [ "${config.boot.initrd.systemd.package}/lib/systemd/systemd-resolved" ];
|
||||
|
||||
@@ -65,6 +65,7 @@ let
|
||||
# Udev.
|
||||
"systemd-udevd-control.socket"
|
||||
"systemd-udevd-kernel.socket"
|
||||
"systemd-udevd-varlink.socket"
|
||||
"systemd-udevd.service"
|
||||
]
|
||||
++ (optional (!config.boot.isContainer) "systemd-udev-trigger.service")
|
||||
@@ -157,6 +158,8 @@ let
|
||||
"systemd-ask-password-wall.service"
|
||||
|
||||
# Varlink APIs
|
||||
"systemd-ask-password@.service"
|
||||
"systemd-ask-password.socket"
|
||||
]
|
||||
++ lib.optionals cfg.package.withBootloader [
|
||||
"systemd-bootctl@.service"
|
||||
@@ -178,11 +181,13 @@ let
|
||||
]
|
||||
++ optionals cfg.package.withImportd [
|
||||
"systemd-importd.service"
|
||||
"systemd-importd.socket"
|
||||
]
|
||||
++ optionals cfg.package.withMachined [
|
||||
"machine.slice"
|
||||
"machines.target"
|
||||
"systemd-machined.service"
|
||||
"systemd-machined.socket"
|
||||
]
|
||||
++ optionals cfg.package.withNspawn [
|
||||
"systemd-nspawn@.service"
|
||||
@@ -191,6 +196,9 @@ let
|
||||
# Misc.
|
||||
"systemd-sysctl.service"
|
||||
"systemd-machine-id-commit.service"
|
||||
|
||||
"systemd-mute-console@.service"
|
||||
"systemd-mute-console.socket"
|
||||
]
|
||||
++ optionals cfg.package.withTimedated [
|
||||
"dbus-org.freedesktop.timedate1.service"
|
||||
@@ -209,6 +217,11 @@ let
|
||||
"dbus-org.freedesktop.portable1.service"
|
||||
"systemd-portabled.service"
|
||||
]
|
||||
++ optionals cfg.package.withRepart [
|
||||
# Varlink APIs
|
||||
"systemd-repart@.service"
|
||||
"systemd-repart.socket"
|
||||
]
|
||||
++ [
|
||||
"systemd-exit.service"
|
||||
"systemd-update-done.service"
|
||||
@@ -221,6 +234,8 @@ let
|
||||
"factory-reset.target"
|
||||
"systemd-factory-reset-request.service"
|
||||
"systemd-factory-reset-reboot.service"
|
||||
"systemd-factory-reset@.service"
|
||||
"systemd-factory-reset.socket"
|
||||
]
|
||||
++ cfg.additionalUpstreamSystemUnits;
|
||||
|
||||
|
||||
@@ -157,6 +157,12 @@ in
|
||||
boot.initrd.systemd = lib.mkIf initrdCfg.enable {
|
||||
additionalUpstreamUnits = [
|
||||
"systemd-repart.service"
|
||||
# Varlink APIs
|
||||
# NOTE: compared to stage 2 where the IPC is enabled in the global location, initrd
|
||||
# might be optimized to keep away the repart binary.
|
||||
# As a result, we enable repart IPC in the initrd only if repart is enabled in the initrd.
|
||||
"systemd-repart.socket"
|
||||
"systemd-repart@.service"
|
||||
];
|
||||
|
||||
storePaths = [
|
||||
|
||||
@@ -317,6 +317,7 @@ in
|
||||
mkdir -p $out/lib/tmpfiles.d
|
||||
cd $out/lib/tmpfiles.d
|
||||
|
||||
ln -s "${systemd}/example/tmpfiles.d/credstore.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/home.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
|
||||
ln -s "${systemd}/example/tmpfiles.d/portables.conf"
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
"tpm2.target"
|
||||
"systemd-tpm2-setup-early.service"
|
||||
"systemd-tpm2-setup.service"
|
||||
"systemd-pcrextend.socket"
|
||||
"systemd-pcrextend@.service"
|
||||
];
|
||||
}
|
||||
)
|
||||
@@ -69,6 +71,8 @@
|
||||
boot.initrd.systemd.additionalUpstreamUnits = [
|
||||
"tpm2.target"
|
||||
"systemd-tpm2-setup-early.service"
|
||||
"systemd-pcrextend.socket"
|
||||
"systemd-pcrextend@.service"
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [
|
||||
@@ -81,6 +85,7 @@
|
||||
pkgs.tpm2-tss
|
||||
"${cfg.package}/lib/systemd/systemd-tpm2-setup"
|
||||
"${cfg.package}/lib/systemd/system-generators/systemd-tpm2-generator"
|
||||
"${cfg.package}/lib/systemd/systemd-pcrextend"
|
||||
];
|
||||
}
|
||||
)
|
||||
@@ -89,7 +94,9 @@
|
||||
cfg = config.boot.initrd.systemd;
|
||||
in
|
||||
lib.mkIf (cfg.enable && cfg.tpm2.enable && cfg.tpm2.pcrphases.enable) {
|
||||
boot.initrd.systemd.additionalUpstreamUnits = [ "systemd-pcrphase-initrd.service" ];
|
||||
boot.initrd.systemd.additionalUpstreamUnits = [
|
||||
"systemd-pcrphase-initrd.service"
|
||||
];
|
||||
boot.initrd.systemd.services.systemd-pcrphase-initrd.wantedBy = [ "initrd.target" ];
|
||||
boot.initrd.systemd.storePaths = [ "${cfg.package}/lib/systemd/systemd-pcrextend" ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
options.virtualisation.credentials = lib.mkOption {
|
||||
description = ''
|
||||
Credentials to pass to the VM or container using systemd's credential system.
|
||||
|
||||
See {manpage}`systemd.exec(5)`, {manpage}`systemd-creds(1)` and https://systemd.io/CREDENTIALS/ for more
|
||||
information about systemd credentials.
|
||||
'';
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
database-password = {
|
||||
text = "my-secret-password";
|
||||
};
|
||||
ssl-cert = {
|
||||
source = "./cert.pem";
|
||||
};
|
||||
binary-key = {
|
||||
source = "./private.der";
|
||||
};
|
||||
}
|
||||
'';
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule (
|
||||
{
|
||||
name,
|
||||
options,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
options = {
|
||||
source = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.pathWith { });
|
||||
default = null;
|
||||
description = ''
|
||||
Source file on the host containing the credential data.
|
||||
'';
|
||||
};
|
||||
text = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Text content of the credential.
|
||||
|
||||
For binary data or when the credential content should come from
|
||||
an existing file, use `source` instead.
|
||||
|
||||
::: {.warning}
|
||||
The text here is stored in the host's nix store as a file.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
};
|
||||
config.source = lib.mkIf (config.text != null) (
|
||||
lib.mkDerivedConfig options.text (pkgs.writeText name)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -21,6 +21,10 @@ let
|
||||
cfg = config.virtualisation;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../credentials-options.nix
|
||||
];
|
||||
|
||||
options = {
|
||||
|
||||
virtualisation.cmdline = lib.mkOption {
|
||||
@@ -131,7 +135,8 @@ in
|
||||
|
||||
# Send a READY=1 notification to a socket when the container is fully booted.
|
||||
"--notify-ready=yes"
|
||||
];
|
||||
]
|
||||
++ lib.mapAttrsToList (name: cred: "--load-credential=${name}:${cred.source}") cfg.credentials;
|
||||
|
||||
system.build.nspawn =
|
||||
let
|
||||
|
||||
@@ -399,6 +399,7 @@ in
|
||||
imports = [
|
||||
../profiles/qemu-guest.nix
|
||||
./disk-size-option.nix
|
||||
./credentials-options.nix
|
||||
(mkRenamedOptionModule
|
||||
[
|
||||
"virtualisation"
|
||||
@@ -1126,81 +1127,20 @@ in
|
||||
};
|
||||
|
||||
virtualisation.credentials = mkOption {
|
||||
description = ''
|
||||
Credentials to pass to the VM using systemd's credential system.
|
||||
|
||||
See {manpage}`systemd.exec(5)` , {manpage}`systemd-creds(1)` and https://systemd.io/CREDENTIALS/ for more
|
||||
information about systemd credentials.
|
||||
'';
|
||||
default = { };
|
||||
example = {
|
||||
database-password = {
|
||||
text = "my-secret-password";
|
||||
};
|
||||
ssl-cert = {
|
||||
source = "./cert.pem";
|
||||
};
|
||||
binary-key = {
|
||||
mechanism = "fw_cfg";
|
||||
source = "./private.der";
|
||||
};
|
||||
config-file = {
|
||||
mechanism = "smbios";
|
||||
text = ''
|
||||
[database]
|
||||
host=localhost
|
||||
port=5432
|
||||
'';
|
||||
};
|
||||
};
|
||||
type = types.attrsOf (
|
||||
lib.types.submodule (
|
||||
{
|
||||
name,
|
||||
options,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
options = {
|
||||
mechanism = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"fw_cfg"
|
||||
"smbios"
|
||||
];
|
||||
default = if pkgs.stdenv.hostPlatform.isx86 then "smbios" else "fw_cfg";
|
||||
defaultText = lib.literalExpression ''if pkgs.stdenv.hostPlatform.isx86 then "smbios" else "fw_cfg"'';
|
||||
description = ''
|
||||
The mechanism used to pass the credential to the VM.
|
||||
'';
|
||||
};
|
||||
source = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.pathWith { });
|
||||
default = null;
|
||||
description = ''
|
||||
Source file on the host containing the credential data.
|
||||
'';
|
||||
};
|
||||
text = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Text content of the credential.
|
||||
|
||||
For binary data or when the credential content should come from
|
||||
an existing file, use `source` instead.
|
||||
|
||||
::: {.warning}
|
||||
The text here is stored in the host's nix store as a file.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
};
|
||||
config.source = lib.mkIf (config.text != null) (
|
||||
lib.mkDerivedConfig options.text (pkgs.writeText name)
|
||||
);
|
||||
}
|
||||
)
|
||||
lib.types.submodule {
|
||||
options.mechanism = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"fw_cfg"
|
||||
"smbios"
|
||||
];
|
||||
default = if pkgs.stdenv.hostPlatform.isx86 then "smbios" else "fw_cfg";
|
||||
defaultText = lib.literalExpression ''if pkgs.stdenv.hostPlatform.isx86 then "smbios" else "fw_cfg"'';
|
||||
description = ''
|
||||
The mechanism used to pass the credential to the VM.
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -446,6 +446,14 @@ in
|
||||
};
|
||||
coturn = runTest ./coturn.nix;
|
||||
couchdb = runTest ./couchdb.nix;
|
||||
credentials-fwcfg = runTest {
|
||||
imports = [ ./credentials.nix ];
|
||||
_module.args.mechanism = "fw_cfg";
|
||||
};
|
||||
credentials-smbios = runTestOn [ "x86_64-linux" ] {
|
||||
imports = [ ./credentials.nix ];
|
||||
_module.args.mechanism = "smbios";
|
||||
};
|
||||
cri-o = runTestOn [ "aarch64-linux" "x86_64-linux" ] ./cri-o.nix;
|
||||
croc = runTest ./croc.nix;
|
||||
cross-seed = runTest ./cross-seed.nix;
|
||||
@@ -1423,14 +1431,6 @@ in
|
||||
pykms = runTest ./pykms.nix;
|
||||
qbittorrent = runTest ./qbittorrent.nix;
|
||||
qboot = runTestOn [ "x86_64-linux" "i686-linux" ] ./qboot.nix;
|
||||
qemu-vm-credentials-fwcfg = runTest {
|
||||
imports = [ ./qemu-vm-credentials.nix ];
|
||||
_module.args.mechanism = "fw_cfg";
|
||||
};
|
||||
qemu-vm-credentials-smbios = runTestOn [ "x86_64-linux" ] {
|
||||
imports = [ ./qemu-vm-credentials.nix ];
|
||||
_module.args.mechanism = "smbios";
|
||||
};
|
||||
qemu-vm-external-disk-image = runTest ./qemu-vm-external-disk-image.nix;
|
||||
qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix { };
|
||||
qemu-vm-store = runTest ./qemu-vm-store.nix;
|
||||
@@ -1713,6 +1713,7 @@ in
|
||||
systemd-user-settings = runTest ./systemd-user-settings.nix;
|
||||
systemd-user-tmpfiles-rules = runTest ./systemd-user-tmpfiles-rules.nix;
|
||||
systemd-userdbd = runTest ./systemd-userdbd.nix;
|
||||
systemd-varlink = runTest ./systemd-varlink.nix;
|
||||
systemtap = handleTest ./systemtap.nix { };
|
||||
szurubooru = handleTest ./szurubooru.nix { };
|
||||
taler = handleTest ./taler { };
|
||||
|
||||
@@ -207,7 +207,7 @@ in
|
||||
import os
|
||||
|
||||
# Create a mutable linked image backed by the read-only SD image
|
||||
if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0:
|
||||
if os.system("${pkgs.qemu}/bin/qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0:
|
||||
raise RuntimeError("Could not create mutable linked image")
|
||||
|
||||
machine = create_machine("${startCommand}")
|
||||
|
||||
@@ -51,9 +51,11 @@ in
|
||||
)
|
||||
os.makedirs(image_dir, mode=0o700, exist_ok=True)
|
||||
disk_image = os.path.join(image_dir, "machine.qcow2")
|
||||
QEMU_BIN = "${pkgs.qemu}"
|
||||
QEMU_IMG = f"{QEMU_BIN}/bin/qemu-img"
|
||||
subprocess.check_call(
|
||||
[
|
||||
"qemu-img",
|
||||
QEMU_IMG,
|
||||
"create",
|
||||
"-f",
|
||||
"qcow2",
|
||||
@@ -64,7 +66,7 @@ in
|
||||
disk_image,
|
||||
]
|
||||
)
|
||||
subprocess.check_call(["qemu-img", "resize", disk_image, "10G"])
|
||||
subprocess.check_call([QEMU_IMG, "resize", disk_image, "10G"])
|
||||
|
||||
# Note: we use net=169.0.0.0/8 rather than
|
||||
# net=169.254.0.0/16 to prevent dhcpcd from getting horribly
|
||||
@@ -74,7 +76,7 @@ in
|
||||
# turn off the DHCP server, but qemu does not have an option
|
||||
# to do that.
|
||||
start_command = (
|
||||
"qemu-kvm -m 1024"
|
||||
f"{QEMU_BIN}/bin/qemu-kvm -m 1024"
|
||||
+ " -device virtio-net-pci,netdev=vlan0"
|
||||
+ " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${getExe imdsServer} ${metaData}'"
|
||||
+ f" -drive file={disk_image},if=virtio,werror=report"
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
mechanism,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
secret = ''
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
'';
|
||||
secret-file = "bar";
|
||||
|
||||
common-credentials = {
|
||||
secret-default-mechanism = {
|
||||
text = "default-mechanism";
|
||||
};
|
||||
secret-file-nix-store = {
|
||||
source = pkgs.writeText "secret-file-nix-store" secret-file;
|
||||
};
|
||||
secret-file-host = {
|
||||
source = "./secret-file-host";
|
||||
};
|
||||
secret-file-host-binary = {
|
||||
source = "./secret-file-host-binary";
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
name = "credentials-${mechanism}";
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ arianvp ];
|
||||
|
||||
# No VM<->container traffic in this test; credentials are static.
|
||||
requiredFeatures.devnet = lib.mkForce false;
|
||||
|
||||
nodes.vm = {
|
||||
virtualisation.credentials = common-credentials // {
|
||||
secret = {
|
||||
inherit mechanism;
|
||||
text = secret;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
containers.container = {
|
||||
virtualisation.credentials = common-credentials // {
|
||||
secret = {
|
||||
text = secret;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import base64
|
||||
|
||||
secret_file_host = "baz"
|
||||
# Binary data with null bytes, high bytes, and other problematic characters.
|
||||
secret_file_host_binary = bytes([
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0xDE, 0xAD, 0xBE, 0xEF,
|
||||
0xFF, 0xFE, 0xFD, 0xFC,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x80, 0x81, 0x82, 0x83,
|
||||
])
|
||||
|
||||
def assert_credentials(m):
|
||||
with open(m.state_dir / "secret-file-host", "w") as f:
|
||||
f.write(secret_file_host)
|
||||
with open(m.state_dir / "secret-file-host-binary", "wb") as f2:
|
||||
f2.write(secret_file_host_binary)
|
||||
|
||||
t.assertEqual(
|
||||
m.succeed("systemd-creds --system cat secret").strip(),
|
||||
"foo\nbar\nbaz",
|
||||
)
|
||||
t.assertEqual(
|
||||
m.succeed("systemd-creds --system cat secret-default-mechanism").strip(),
|
||||
"default-mechanism",
|
||||
)
|
||||
t.assertEqual(
|
||||
m.succeed("systemd-creds --system cat secret-file-nix-store").strip(),
|
||||
"${secret-file}",
|
||||
)
|
||||
t.assertEqual(
|
||||
m.succeed("systemd-creds --system cat secret-file-host").strip(),
|
||||
secret_file_host,
|
||||
)
|
||||
result = m.succeed(
|
||||
"systemd-creds --system cat secret-file-host-binary --transcode=base64"
|
||||
).strip()
|
||||
expected = base64.b64encode(secret_file_host_binary).decode("ascii")
|
||||
t.assertEqual(
|
||||
result,
|
||||
expected,
|
||||
f"Binary credential mismatch: got {result}, expected {expected}",
|
||||
)
|
||||
|
||||
assert_credentials(vm)
|
||||
assert_credentials(container)
|
||||
'';
|
||||
}
|
||||
+243
-239
@@ -88,294 +88,298 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
# Instance Metadata Service (IMDSv2 with 1.0 metadata version)
|
||||
# TODO: Use 'latest' metadata version instead of '1.0'
|
||||
# TODO: [Test matrix] also test providing the host key through IMDS
|
||||
# - i.e. a test module argument to select between writing or reading the host key
|
||||
def create_ec2_metadata_dir(temp_dir, client_pubkey):
|
||||
"""Create fake EC2 metadata directory structure with mock data"""
|
||||
metadata_dir = os.path.join(temp_dir.name, "ec2-metadata")
|
||||
# Instance Metadata Service (IMDSv2 with 1.0 metadata version)
|
||||
# TODO: Use 'latest' metadata version instead of '1.0'
|
||||
# TODO: [Test matrix] also test providing the host key through IMDS
|
||||
# - i.e. a test module argument to select between writing or reading the host key
|
||||
def create_ec2_metadata_dir(temp_dir, client_pubkey):
|
||||
"""Create fake EC2 metadata directory structure with mock data"""
|
||||
metadata_dir = os.path.join(temp_dir.name, "ec2-metadata")
|
||||
|
||||
# Create directory structure
|
||||
os.makedirs(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0"), exist_ok=True)
|
||||
os.makedirs(os.path.join(metadata_dir, "latest", "api"), exist_ok=True)
|
||||
# Create directory structure
|
||||
os.makedirs(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0"), exist_ok=True)
|
||||
os.makedirs(os.path.join(metadata_dir, "latest", "api"), exist_ok=True)
|
||||
|
||||
# Metadata version 1.0 endpoints (what fetch-ec2-metadata.sh actually fetches)
|
||||
with open(os.path.join(metadata_dir, "1.0", "meta-data", "hostname"), "w") as f:
|
||||
f.write("test-instance")
|
||||
with open(os.path.join(metadata_dir, "1.0", "meta-data", "ami-manifest-path"), "w") as f:
|
||||
f.write("(test)")
|
||||
with open(os.path.join(metadata_dir, "1.0", "meta-data", "instance-id"), "w") as f:
|
||||
f.write("i-1234567890abcdef0")
|
||||
with open(os.path.join(metadata_dir, "1.0", "user-data"), "w") as f:
|
||||
f.write("")
|
||||
with open(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0", "openssh-key"), "w") as f:
|
||||
f.write(client_pubkey)
|
||||
# Metadata version 1.0 endpoints (what fetch-ec2-metadata.sh actually fetches)
|
||||
with open(os.path.join(metadata_dir, "1.0", "meta-data", "hostname"), "w") as f:
|
||||
f.write("test-instance")
|
||||
with open(os.path.join(metadata_dir, "1.0", "meta-data", "ami-manifest-path"), "w") as f:
|
||||
f.write("(test)")
|
||||
with open(os.path.join(metadata_dir, "1.0", "meta-data", "instance-id"), "w") as f:
|
||||
f.write("i-1234567890abcdef0")
|
||||
with open(os.path.join(metadata_dir, "1.0", "user-data"), "w") as f:
|
||||
f.write("")
|
||||
with open(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0", "openssh-key"), "w") as f:
|
||||
f.write(client_pubkey)
|
||||
|
||||
# IMDSv2 token endpoint - return a fake token
|
||||
with open(os.path.join(metadata_dir, "latest", "api", "token"), "w") as f:
|
||||
f.write("test-token-12345")
|
||||
# IMDSv2 token endpoint - return a fake token
|
||||
with open(os.path.join(metadata_dir, "latest", "api", "token"), "w") as f:
|
||||
f.write("test-token-12345")
|
||||
|
||||
return metadata_dir
|
||||
return metadata_dir
|
||||
|
||||
def generate_client_ssh_key():
|
||||
"""Generate SSH key pair on VM host for client authentication"""
|
||||
# Use temporary directory for key generation
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory() as key_dir:
|
||||
private_key = os.path.join(key_dir, "id_ed25519")
|
||||
public_key = os.path.join(key_dir, "id_ed25519.pub")
|
||||
def generate_client_ssh_key():
|
||||
"""Generate SSH key pair on VM host for client authentication"""
|
||||
# Use temporary directory for key generation
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory() as key_dir:
|
||||
private_key = os.path.join(key_dir, "id_ed25519")
|
||||
public_key = os.path.join(key_dir, "id_ed25519.pub")
|
||||
|
||||
# Generate key pair using host SSH tools
|
||||
ret = os.system(f"${hostPkgs.openssh}/bin/ssh-keygen -t ed25519 -f {private_key} -N \"\"")
|
||||
if ret != 0:
|
||||
raise Exception("Failed to generate SSH key pair")
|
||||
# Generate key pair using host SSH tools
|
||||
ret = os.system(f"${hostPkgs.openssh}/bin/ssh-keygen -t ed25519 -f {private_key} -N \"\"")
|
||||
if ret != 0:
|
||||
raise Exception("Failed to generate SSH key pair")
|
||||
|
||||
# Read the generated public key
|
||||
with open(public_key, "r") as f:
|
||||
client_pubkey = f.read().strip()
|
||||
# Read the generated public key
|
||||
with open(public_key, "r") as f:
|
||||
client_pubkey = f.read().strip()
|
||||
|
||||
# Read the private key
|
||||
with open(private_key, "r") as f:
|
||||
client_private_key = f.read()
|
||||
# Read the private key
|
||||
with open(private_key, "r") as f:
|
||||
client_private_key = f.read()
|
||||
|
||||
return client_pubkey, client_private_key
|
||||
return client_pubkey, client_private_key
|
||||
|
||||
def setup_client_ssh_key(client, client_private_key):
|
||||
"""Install the pre-generated SSH private key on client"""
|
||||
client.succeed("mkdir -p /root/.ssh")
|
||||
client.succeed(f"cat > /root/.ssh/id_ed25519 << 'EOF'\n{client_private_key}\nEOF")
|
||||
client.succeed("chmod 600 /root/.ssh/id_ed25519")
|
||||
def setup_client_ssh_key(client, client_private_key):
|
||||
"""Install the pre-generated SSH private key on client"""
|
||||
client.succeed("mkdir -p /root/.ssh")
|
||||
client.succeed(f"cat > /root/.ssh/id_ed25519 << 'EOF'\n{client_private_key}\nEOF")
|
||||
client.succeed("chmod 600 /root/.ssh/id_ed25519")
|
||||
|
||||
def setup_machine(temp_dir, client_pubkey):
|
||||
"""Initialize EC2 machine with disk image, metadata server, and networking"""
|
||||
# Set up disk image
|
||||
image_dir = os.path.join(
|
||||
os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine"
|
||||
)
|
||||
os.makedirs(image_dir, mode=0o700, exist_ok=True)
|
||||
disk_image = os.path.join(image_dir, "machine.qcow2")
|
||||
subprocess.check_call([
|
||||
"qemu-img", "create", "-f", "qcow2", "-F", "qcow2",
|
||||
"-o", "backing_file=${image}", disk_image
|
||||
])
|
||||
subprocess.check_call(["qemu-img", "resize", disk_image, "10G"])
|
||||
def setup_machine(temp_dir, client_pubkey):
|
||||
"""Initialize EC2 machine with disk image, metadata server, and networking"""
|
||||
# Set up disk image
|
||||
image_dir = os.path.join(
|
||||
os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine"
|
||||
)
|
||||
os.makedirs(image_dir, mode=0o700, exist_ok=True)
|
||||
disk_image = os.path.join(image_dir, "machine.qcow2")
|
||||
QEMU = "${nodes.machine.virtualisation.qemu.package}"
|
||||
QEMU_IMG = f"{QEMU}/bin/qemu-img"
|
||||
subprocess.check_call([
|
||||
QEMU_IMG, "create", "-f", "qcow2", "-F", "qcow2",
|
||||
"-o", "backing_file=${image}", disk_image
|
||||
])
|
||||
subprocess.check_call([QEMU_IMG, "resize", disk_image, "10G"])
|
||||
|
||||
# Create fake EC2 metadata in temporary directory with client's public key
|
||||
metadata_dir = create_ec2_metadata_dir(temp_dir, client_pubkey)
|
||||
# Create fake EC2 metadata in temporary directory with client's public key
|
||||
metadata_dir = create_ec2_metadata_dir(temp_dir, client_pubkey)
|
||||
|
||||
# Add both VLAN networking (matching test framework) and EC2 metadata server
|
||||
vlan_net = (
|
||||
" -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:01:02"
|
||||
+ ' -netdev vde,id=vlan1,sock="$QEMU_VDE_SOCKET_1"'
|
||||
)
|
||||
metadata_net = (
|
||||
" -device virtio-net-pci,netdev=ec2meta"
|
||||
+ f" -netdev 'user,id=ec2meta,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${lib.getExe imdsServer} {metadata_dir}'"
|
||||
)
|
||||
# Add both VLAN networking (matching test framework) and EC2 metadata server
|
||||
vlan_net = (
|
||||
" -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:01:02"
|
||||
+ ' -netdev vde,id=vlan1,sock="$QEMU_VDE_SOCKET_1"'
|
||||
)
|
||||
metadata_net = (
|
||||
" -device virtio-net-pci,netdev=ec2meta"
|
||||
+ f" -netdev 'user,id=ec2meta,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${lib.getExe imdsServer} {metadata_dir}'"
|
||||
)
|
||||
|
||||
start_command = (
|
||||
"qemu-kvm -m 1024"
|
||||
+ f" -drive file={disk_image},if=virtio,werror=report"
|
||||
+ vlan_net
|
||||
+ metadata_net
|
||||
+ " $QEMU_OPTS"
|
||||
)
|
||||
start_command = (
|
||||
f"{QEMU}/bin/qemu-kvm -m 1024"
|
||||
+ f" -drive file={disk_image},if=virtio,werror=report"
|
||||
+ vlan_net
|
||||
+ metadata_net
|
||||
+ " $QEMU_OPTS"
|
||||
)
|
||||
|
||||
return create_machine(start_command), metadata_dir
|
||||
return create_machine(start_command), metadata_dir
|
||||
|
||||
def test_userdata_decompression(machine, user_data_path, compressed_data, format_name):
|
||||
"""Test that compressed user-data is decompressed by fetch-ec2-metadata"""
|
||||
test_marker = f"{format_name}-decompression-test"
|
||||
with open(user_data_path, "wb") as f:
|
||||
f.write(compressed_data)
|
||||
machine.succeed("systemctl reset-failed fetch-ec2-metadata; systemctl restart fetch-ec2-metadata")
|
||||
result = machine.succeed("cat /etc/ec2-metadata/user-data")
|
||||
assert test_marker in result, f"Expected '{test_marker}' in decompressed {format_name} content, got: {result}"
|
||||
journal = machine.succeed("journalctl -u fetch-ec2-metadata --no-pager -b")
|
||||
assert "decompressing:" in journal, f"Expected decompression log in journal for {format_name}"
|
||||
def test_userdata_decompression(machine, user_data_path, compressed_data, format_name):
|
||||
"""Test that compressed user-data is decompressed by fetch-ec2-metadata"""
|
||||
test_marker = f"{format_name}-decompression-test"
|
||||
with open(user_data_path, "wb") as f:
|
||||
f.write(compressed_data)
|
||||
machine.succeed("systemctl reset-failed fetch-ec2-metadata; systemctl restart fetch-ec2-metadata")
|
||||
result = machine.succeed("cat /etc/ec2-metadata/user-data")
|
||||
assert test_marker in result, f"Expected '{test_marker}' in decompressed {format_name} content, got: {result}"
|
||||
journal = machine.succeed("journalctl -u fetch-ec2-metadata --no-pager -b")
|
||||
assert "decompressing:" in journal, f"Expected decompression log in journal for {format_name}"
|
||||
|
||||
# Create temporary directory for metadata (scoped for cleanup)
|
||||
temp_dir = tempfile.TemporaryDirectory()
|
||||
# Create temporary directory for metadata (scoped for cleanup)
|
||||
temp_dir = tempfile.TemporaryDirectory()
|
||||
|
||||
# Start client first (but don't wait for it to boot)
|
||||
client.start()
|
||||
# Start client first (but don't wait for it to boot)
|
||||
client.start()
|
||||
|
||||
# Generate SSH key pair on VM host before starting machine
|
||||
client_pubkey, client_private_key = generate_client_ssh_key()
|
||||
# Generate SSH key pair on VM host before starting machine
|
||||
client_pubkey, client_private_key = generate_client_ssh_key()
|
||||
|
||||
# Set up machine with client's public key in metadata service
|
||||
machine, metadata_dir = setup_machine(temp_dir, client_pubkey)
|
||||
user_data_path = os.path.join(metadata_dir, "1.0", "user-data")
|
||||
# Set up machine with client's public key in metadata service
|
||||
machine, metadata_dir = setup_machine(temp_dir, client_pubkey)
|
||||
user_data_path = os.path.join(metadata_dir, "1.0", "user-data")
|
||||
|
||||
try:
|
||||
machine.start()
|
||||
try:
|
||||
machine.start()
|
||||
|
||||
# Wait for services to be ready
|
||||
machine.wait_for_unit("sshd.service")
|
||||
machine.wait_for_unit("print-host-key.service")
|
||||
machine.wait_for_unit("apply-ec2-data.service")
|
||||
# Wait for services to be ready
|
||||
machine.wait_for_unit("sshd.service")
|
||||
machine.wait_for_unit("print-host-key.service")
|
||||
machine.wait_for_unit("apply-ec2-data.service")
|
||||
|
||||
# Extract shared variables outside subtests
|
||||
machine_ip = "${config.nodes.machine.networking.primaryIPAddress}"
|
||||
# Extract shared variables outside subtests
|
||||
machine_ip = "${config.nodes.machine.networking.primaryIPAddress}"
|
||||
|
||||
with subtest("EC2 metadata service connectivity"):
|
||||
# Obtain an IMDSv2 token, then use it to fetch metadata
|
||||
imds_token = machine.succeed(
|
||||
"curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'"
|
||||
" http://169.254.169.254/latest/api/token"
|
||||
).strip()
|
||||
assert imds_token, "Failed to obtain IMDSv2 token"
|
||||
hostname_response = machine.succeed(
|
||||
f"curl -sf -H 'X-aws-ec2-metadata-token: {imds_token}'"
|
||||
" http://169.254.169.254/1.0/meta-data/hostname"
|
||||
)
|
||||
assert "test-instance" in hostname_response, f"Expected 'test-instance', got: {hostname_response}"
|
||||
with subtest("EC2 metadata service connectivity"):
|
||||
# Obtain an IMDSv2 token, then use it to fetch metadata
|
||||
imds_token = machine.succeed(
|
||||
"curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'"
|
||||
" http://169.254.169.254/latest/api/token"
|
||||
).strip()
|
||||
assert imds_token, "Failed to obtain IMDSv2 token"
|
||||
hostname_response = machine.succeed(
|
||||
f"curl -sf -H 'X-aws-ec2-metadata-token: {imds_token}'"
|
||||
" http://169.254.169.254/1.0/meta-data/hostname"
|
||||
)
|
||||
assert "test-instance" in hostname_response, f"Expected 'test-instance', got: {hostname_response}"
|
||||
|
||||
with subtest("SSH host key extraction from console"):
|
||||
console_log = machine.get_console_log()
|
||||
assert "-----BEGIN SSH HOST KEY FINGERPRINTS-----" in console_log
|
||||
assert "-----END SSH HOST KEY FINGERPRINTS-----" in console_log
|
||||
assert "-----BEGIN SSH HOST KEY KEYS-----" in console_log
|
||||
assert "-----END SSH HOST KEY KEYS-----" in console_log
|
||||
with subtest("SSH host key extraction from console"):
|
||||
console_log = machine.get_console_log()
|
||||
assert "-----BEGIN SSH HOST KEY FINGERPRINTS-----" in console_log
|
||||
assert "-----END SSH HOST KEY FINGERPRINTS-----" in console_log
|
||||
assert "-----BEGIN SSH HOST KEY KEYS-----" in console_log
|
||||
assert "-----END SSH HOST KEY KEYS-----" in console_log
|
||||
|
||||
keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----"
|
||||
keys_match = re.search(keys_pattern, console_log, re.DOTALL)
|
||||
assert keys_match, "Could not find SSH host keys section"
|
||||
keys_content = keys_match.group(1).strip()
|
||||
assert "ssh-" in keys_content, "SSH keys should contain ssh- prefix"
|
||||
keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----"
|
||||
keys_match = re.search(keys_pattern, console_log, re.DOTALL)
|
||||
assert keys_match, "Could not find SSH host keys section"
|
||||
keys_content = keys_match.group(1).strip()
|
||||
assert "ssh-" in keys_content, "SSH keys should contain ssh- prefix"
|
||||
|
||||
with subtest("Network connectivity"):
|
||||
client.succeed(f"ping -c 1 {machine_ip}")
|
||||
with subtest("Network connectivity"):
|
||||
client.succeed(f"ping -c 1 {machine_ip}")
|
||||
|
||||
with subtest("SSH connectivity with strict host key checking"):
|
||||
# Install the pre-generated private key on client
|
||||
setup_client_ssh_key(client, client_private_key)
|
||||
with subtest("SSH connectivity with strict host key checking"):
|
||||
# Install the pre-generated private key on client
|
||||
setup_client_ssh_key(client, client_private_key)
|
||||
|
||||
# Get console log and extract host keys
|
||||
console_log = machine.get_console_log()
|
||||
keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----"
|
||||
keys_match = re.search(keys_pattern, console_log, re.DOTALL)
|
||||
assert keys_match, "Could not find SSH host keys section"
|
||||
# Get console log and extract host keys
|
||||
console_log = machine.get_console_log()
|
||||
keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----"
|
||||
keys_match = re.search(keys_pattern, console_log, re.DOTALL)
|
||||
assert keys_match, "Could not find SSH host keys section"
|
||||
|
||||
# Create known_hosts file from console-extracted host keys
|
||||
keys_content = keys_match.group(1).strip()
|
||||
known_hosts_entries = []
|
||||
for line in keys_content.split('\n'):
|
||||
if line.strip() and line.startswith('ssh-'):
|
||||
known_hosts_entries.append(f"{machine_ip} {line.strip()}")
|
||||
# Create known_hosts file from console-extracted host keys
|
||||
keys_content = keys_match.group(1).strip()
|
||||
known_hosts_entries = []
|
||||
for line in keys_content.split('\n'):
|
||||
if line.strip() and line.startswith('ssh-'):
|
||||
known_hosts_entries.append(f"{machine_ip} {line.strip()}")
|
||||
|
||||
assert known_hosts_entries, "No SSH host keys found for known_hosts generation"
|
||||
assert known_hosts_entries, "No SSH host keys found for known_hosts generation"
|
||||
|
||||
known_hosts_content = '\n'.join(known_hosts_entries)
|
||||
client.succeed(f"cat > /root/.ssh/known_hosts << 'EOF'\n{known_hosts_content}\nEOF")
|
||||
known_hosts_content = '\n'.join(known_hosts_entries)
|
||||
client.succeed(f"cat > /root/.ssh/known_hosts << 'EOF'\n{known_hosts_content}\nEOF")
|
||||
|
||||
# Test SSH connectivity with strict host key checking
|
||||
ssh_result = client.succeed(f"ssh -o ConnectTimeout=60 -o BatchMode=yes -i /root/.ssh/id_ed25519 root@{machine_ip} 'echo Hello from $(hostname)'")
|
||||
assert "Hello from test-instance" in ssh_result, f"Unexpected SSH result: {ssh_result}"
|
||||
# Test SSH connectivity with strict host key checking
|
||||
ssh_result = client.succeed(f"ssh -o ConnectTimeout=60 -o BatchMode=yes -i /root/.ssh/id_ed25519 root@{machine_ip} 'echo Hello from $(hostname)'")
|
||||
assert "Hello from test-instance" in ssh_result, f"Unexpected SSH result: {ssh_result}"
|
||||
|
||||
with subtest("Basic EC2 functionality"):
|
||||
machine.succeed("findmnt / -o SIZE -n | grep -E '[0-9]+G'")
|
||||
with subtest("Basic EC2 functionality"):
|
||||
machine.succeed("findmnt / -o SIZE -n | grep -E '[0-9]+G'")
|
||||
|
||||
with subtest("Decompression of gzip-compressed user-data"):
|
||||
import gzip as gzip_mod
|
||||
test_data = b"#!/bin/bash\necho gzip-decompression-test\n"
|
||||
test_userdata_decompression(machine, user_data_path, gzip_mod.compress(test_data), "gzip")
|
||||
with subtest("Decompression of gzip-compressed user-data"):
|
||||
import gzip as gzip_mod
|
||||
test_data = b"#!/bin/bash\necho gzip-decompression-test\n"
|
||||
test_userdata_decompression(machine, user_data_path, gzip_mod.compress(test_data), "gzip")
|
||||
|
||||
with subtest("Decompression of bzip2-compressed user-data"):
|
||||
import bz2
|
||||
test_data = b"#!/bin/bash\necho bzip2-decompression-test\n"
|
||||
test_userdata_decompression(machine, user_data_path, bz2.compress(test_data), "bzip2")
|
||||
with subtest("Decompression of bzip2-compressed user-data"):
|
||||
import bz2
|
||||
test_data = b"#!/bin/bash\necho bzip2-decompression-test\n"
|
||||
test_userdata_decompression(machine, user_data_path, bz2.compress(test_data), "bzip2")
|
||||
|
||||
with subtest("Decompression of xz-compressed user-data"):
|
||||
import lzma
|
||||
test_data = b"#!/bin/bash\necho xz-decompression-test\n"
|
||||
test_userdata_decompression(machine, user_data_path, lzma.compress(test_data), "xz")
|
||||
with subtest("Decompression of xz-compressed user-data"):
|
||||
import lzma
|
||||
test_data = b"#!/bin/bash\necho xz-decompression-test\n"
|
||||
test_userdata_decompression(machine, user_data_path, lzma.compress(test_data), "xz")
|
||||
|
||||
with subtest("Decompression of zstd-compressed user-data"):
|
||||
test_data = b"#!/bin/bash\necho zstd-decompression-test\n"
|
||||
proc = subprocess.run(
|
||||
["${hostPkgs.zstd}/bin/zstd", "-c"],
|
||||
input=test_data, capture_output=True, check=True,
|
||||
)
|
||||
test_userdata_decompression(machine, user_data_path, proc.stdout, "zstd")
|
||||
with subtest("Decompression of zstd-compressed user-data"):
|
||||
test_data = b"#!/bin/bash\necho zstd-decompression-test\n"
|
||||
proc = subprocess.run(
|
||||
["${hostPkgs.zstd}/bin/zstd", "-c"],
|
||||
input=test_data, capture_output=True, check=True,
|
||||
)
|
||||
test_userdata_decompression(machine, user_data_path, proc.stdout, "zstd")
|
||||
|
||||
with subtest("Decompression of lzip-compressed user-data"):
|
||||
test_data = b"#!/bin/bash\necho lzip-decompression-test\n"
|
||||
proc = subprocess.run(
|
||||
["${hostPkgs.lzip}/bin/lzip", "-c"],
|
||||
input=test_data, capture_output=True, check=True,
|
||||
)
|
||||
test_userdata_decompression(machine, user_data_path, proc.stdout, "lzip")
|
||||
with subtest("Decompression of lzip-compressed user-data"):
|
||||
test_data = b"#!/bin/bash\necho lzip-decompression-test\n"
|
||||
proc = subprocess.run(
|
||||
["${hostPkgs.lzip}/bin/lzip", "-c"],
|
||||
input=test_data, capture_output=True, check=True,
|
||||
)
|
||||
test_userdata_decompression(machine, user_data_path, proc.stdout, "lzip")
|
||||
|
||||
with subtest("IPv6 IMDS fallback"):
|
||||
# Save hostname fetched via IPv4 for later comparison
|
||||
original_hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip()
|
||||
with subtest("IPv6 IMDS fallback"):
|
||||
# Save hostname fetched via IPv4 for later comparison
|
||||
original_hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip()
|
||||
|
||||
# Assign the EC2 IPv6 IMDS address to loopback
|
||||
machine.succeed("ip -6 addr add fd00:ec2::254/128 dev lo")
|
||||
# Assign the EC2 IPv6 IMDS address to loopback
|
||||
machine.succeed("ip -6 addr add fd00:ec2::254/128 dev lo")
|
||||
|
||||
# Create metadata directory structure for the IPv6 endpoint
|
||||
machine.succeed(
|
||||
"mkdir -p /tmp/ipv6-metadata/1.0/meta-data/public-keys/0"
|
||||
" && mkdir -p /tmp/ipv6-metadata/latest/api"
|
||||
" && cp /etc/ec2-metadata/hostname /tmp/ipv6-metadata/1.0/meta-data/hostname"
|
||||
" && cp /etc/ec2-metadata/ami-manifest-path /tmp/ipv6-metadata/1.0/meta-data/ami-manifest-path"
|
||||
" && echo i-1234567890abcdef0 > /tmp/ipv6-metadata/1.0/meta-data/instance-id"
|
||||
" && echo ipv6-test-token > /tmp/ipv6-metadata/latest/api/token"
|
||||
" && touch /tmp/ipv6-metadata/1.0/user-data"
|
||||
)
|
||||
machine.execute(
|
||||
"test -f /etc/ec2-metadata/public-keys-0-openssh-key"
|
||||
" && cp /etc/ec2-metadata/public-keys-0-openssh-key"
|
||||
" /tmp/ipv6-metadata/1.0/meta-data/public-keys/0/openssh-key"
|
||||
)
|
||||
# Create metadata directory structure for the IPv6 endpoint
|
||||
machine.succeed(
|
||||
"mkdir -p /tmp/ipv6-metadata/1.0/meta-data/public-keys/0"
|
||||
" && mkdir -p /tmp/ipv6-metadata/latest/api"
|
||||
" && cp /etc/ec2-metadata/hostname /tmp/ipv6-metadata/1.0/meta-data/hostname"
|
||||
" && cp /etc/ec2-metadata/ami-manifest-path /tmp/ipv6-metadata/1.0/meta-data/ami-manifest-path"
|
||||
" && echo i-1234567890abcdef0 > /tmp/ipv6-metadata/1.0/meta-data/instance-id"
|
||||
" && echo ipv6-test-token > /tmp/ipv6-metadata/latest/api/token"
|
||||
" && touch /tmp/ipv6-metadata/1.0/user-data"
|
||||
)
|
||||
machine.execute(
|
||||
"test -f /etc/ec2-metadata/public-keys-0-openssh-key"
|
||||
" && cp /etc/ec2-metadata/public-keys-0-openssh-key"
|
||||
" /tmp/ipv6-metadata/1.0/meta-data/public-keys/0/openssh-key"
|
||||
)
|
||||
|
||||
# Serve metadata on the IPv6 IMDS address via socat + imds-server (inetd-style)
|
||||
machine.succeed(
|
||||
"systemd-run --unit=ipv6-imds --"
|
||||
" socat TCP6-LISTEN:80,bind=[fd00:ec2::254],fork,reuseaddr"
|
||||
" SYSTEM:'${lib.getExe imdsServer} /tmp/ipv6-metadata'"
|
||||
)
|
||||
# Serve metadata on the IPv6 IMDS address via socat + imds-server (inetd-style)
|
||||
machine.succeed(
|
||||
"systemd-run --unit=ipv6-imds --"
|
||||
" socat TCP6-LISTEN:80,bind=[fd00:ec2::254],fork,reuseaddr"
|
||||
" SYSTEM:'${lib.getExe imdsServer} /tmp/ipv6-metadata'"
|
||||
)
|
||||
|
||||
# Wait for IPv6 IMDS to become reachable (token endpoint doesn't require auth)
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'"
|
||||
" http://[fd00:ec2::254]/latest/api/token"
|
||||
)
|
||||
# Wait for IPv6 IMDS to become reachable (token endpoint doesn't require auth)
|
||||
machine.wait_until_succeeds(
|
||||
"curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'"
|
||||
" http://[fd00:ec2::254]/latest/api/token"
|
||||
)
|
||||
|
||||
# Block IPv4 IMDS to force fallback to IPv6
|
||||
machine.succeed(
|
||||
"iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT"
|
||||
)
|
||||
# Block IPv4 IMDS to force fallback to IPv6
|
||||
machine.succeed(
|
||||
"iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT"
|
||||
)
|
||||
|
||||
# Verify IPv4 IMDS is now unreachable
|
||||
machine.fail(
|
||||
"curl -sf --connect-timeout 2 http://169.254.169.254/1.0/meta-data/hostname"
|
||||
)
|
||||
# Verify IPv4 IMDS is now unreachable
|
||||
machine.fail(
|
||||
"curl -sf --connect-timeout 2 http://169.254.169.254/1.0/meta-data/hostname"
|
||||
)
|
||||
|
||||
# Clear fetched metadata and re-run the fetcher
|
||||
machine.succeed("rm -f /etc/ec2-metadata/*")
|
||||
machine.succeed("systemctl restart fetch-ec2-metadata")
|
||||
# Clear fetched metadata and re-run the fetcher
|
||||
machine.succeed("rm -f /etc/ec2-metadata/*")
|
||||
machine.succeed("systemctl restart fetch-ec2-metadata")
|
||||
|
||||
# Verify metadata was successfully re-fetched via IPv6
|
||||
hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip()
|
||||
assert hostname == original_hostname, f"Expected '{original_hostname}', got '{hostname}'"
|
||||
# Verify metadata was successfully re-fetched via IPv6
|
||||
hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip()
|
||||
assert hostname == original_hostname, f"Expected '{original_hostname}', got '{hostname}'"
|
||||
|
||||
# Clean up: restore IPv4 IMDS access
|
||||
machine.succeed(
|
||||
"iptables -D OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT"
|
||||
)
|
||||
machine.succeed("systemctl stop ipv6-imds")
|
||||
# Clean up: restore IPv4 IMDS access
|
||||
machine.succeed(
|
||||
"iptables -D OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT"
|
||||
)
|
||||
machine.succeed("systemctl stop ipv6-imds")
|
||||
|
||||
finally:
|
||||
machine.shutdown()
|
||||
temp_dir.cleanup()
|
||||
'';
|
||||
finally:
|
||||
machine.shutdown()
|
||||
temp_dir.cleanup()
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
mechanism,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
secret = ''
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
'';
|
||||
secret-file = "bar";
|
||||
in
|
||||
|
||||
{
|
||||
name = "qemu-vm-credentials-${mechanism}";
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ arianvp ];
|
||||
|
||||
nodes = {
|
||||
machine = {
|
||||
virtualisation.credentials = {
|
||||
secret = {
|
||||
inherit mechanism;
|
||||
text = secret;
|
||||
};
|
||||
secret-default-mechanism = {
|
||||
text = "default-mechanism";
|
||||
};
|
||||
secret-file-nix-store = {
|
||||
inherit mechanism;
|
||||
source = pkgs.writeText "secret-file-nix-store" secret-file;
|
||||
};
|
||||
secret-file-host = {
|
||||
inherit mechanism;
|
||||
source = "./secret-file-host";
|
||||
};
|
||||
secret-file-host-binary = {
|
||||
inherit mechanism;
|
||||
source = "./secret-file-host-binary";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import base64
|
||||
secret_file_host = "baz"
|
||||
# Binary data with null bytes, high bytes, and all sorts of problematic characters
|
||||
secret_file_host_binary = bytes([
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, # null and control chars
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0xDE, 0xAD, 0xBE, 0xEF, # classic binary pattern
|
||||
0xFF, 0xFE, 0xFD, 0xFC, # high bytes
|
||||
0x00, 0x00, 0x00, 0x00, # multiple nulls
|
||||
0x80, 0x81, 0x82, 0x83, # more high bytes
|
||||
])
|
||||
|
||||
with open(machine.state_dir / "secret-file-host", "w") as f:
|
||||
f.write(secret_file_host)
|
||||
with open(machine.state_dir / "secret-file-host-binary", "wb") as f2:
|
||||
f2.write(secret_file_host_binary)
|
||||
|
||||
|
||||
# Test text credential
|
||||
t.assertEqual(machine.succeed("systemd-creds --system cat secret").strip(), "foo\nbar\nbaz")
|
||||
|
||||
t.assertEqual(machine.succeed("systemd-creds --system cat secret-default-mechanism").strip(), "default-mechanism")
|
||||
|
||||
# Test credential from nix store
|
||||
t.assertEqual(machine.succeed("systemd-creds --system cat secret-file-nix-store").strip(), "${secret-file}")
|
||||
|
||||
# Test credential from host file
|
||||
t.assertEqual(machine.succeed("systemd-creds --system cat secret-file-host").strip(), secret_file_host)
|
||||
|
||||
# Test binary credential - verify exact binary content
|
||||
result = machine.succeed("systemd-creds --system cat secret-file-host-binary --transcode=base64").strip()
|
||||
expected = base64.b64encode(secret_file_host_binary).decode('ascii')
|
||||
t.assertEqual(result, expected, f"Binary credential mismatch: got {result}, expected {expected}")
|
||||
'';
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "systemd-initrd-simple";
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
testing.initrdBackdoor = true;
|
||||
boot.initrd.systemd.enable = true;
|
||||
virtualisation.fileSystems."/".autoResize = true;
|
||||
};
|
||||
nodes.machine = {
|
||||
testing.initrdBackdoor = true;
|
||||
boot.initrd.systemd.enable = true;
|
||||
virtualisation.fileSystems."/".autoResize = true;
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
# python
|
||||
''
|
||||
import subprocess
|
||||
@@ -43,7 +43,7 @@
|
||||
oldAvail = machine.succeed("df --output=avail / | sed 1d")
|
||||
machine.shutdown()
|
||||
|
||||
subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
|
||||
subprocess.check_call(["${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
|
||||
|
||||
machine.start()
|
||||
machine.switch_root()
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
name = "systemd-varlink";
|
||||
meta.maintainers = [ lib.maintainers.raitobezarius ];
|
||||
nodes.machine =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
networking.useNetworkd = true;
|
||||
services.resolved.enable = true;
|
||||
systemd.network.enable = true;
|
||||
};
|
||||
testScript = ''
|
||||
def list_interfaces(intf_path: str) -> list[str]:
|
||||
return machine.succeed(f"varlinkctl list-interfaces {intf_path}").split('\n')
|
||||
|
||||
expected_reg_sd_interfaces = [
|
||||
("BootControl", "bootctl"),
|
||||
("Credentials", "creds"),
|
||||
("Hostname", "hostnamed"),
|
||||
("JournalAccess", "journald"),
|
||||
("Import", "importd"),
|
||||
("Machine", "machined"),
|
||||
("Resolve", "resolved-varlink"),
|
||||
("Resolve.Monitor", "resolved-monitor"),
|
||||
("Udev", "udevd-varlink"),
|
||||
("MuteConsole", "mute-console"),
|
||||
("FactoryReset", "factory-reset"),
|
||||
("AskPassword", "ask-password"),
|
||||
("Network", "networkd-varlink"),
|
||||
("Repart", "repart"),
|
||||
]
|
||||
expected_priv_sd_interfaces = [
|
||||
("Login", None), # systemd-logind-varlink.socket exist but is not necessary.
|
||||
]
|
||||
expected_interfaces = [
|
||||
(f"io.systemd.{intf}", f"systemd-{socket_name}", f"/run/varlink/registry/io.systemd.{intf}") for intf, socket_name in expected_reg_sd_interfaces
|
||||
] + [
|
||||
(f"io.systemd.{intf}", f"systemd-{socket_name}" if socket_name is not None else None, f"/run/systemd/io.systemd.{intf}") for intf, socket_name in expected_priv_sd_interfaces
|
||||
]
|
||||
|
||||
for intf, socket_name, intf_path in expected_interfaces:
|
||||
if socket_name is not None:
|
||||
machine.wait_for_unit(f"{socket_name}.socket")
|
||||
assert intf in list_interfaces(intf_path), f"Interface '{intf}' not found in the Varlink registry"
|
||||
'';
|
||||
}
|
||||
+18
-16
@@ -38,21 +38,23 @@
|
||||
services.journald.extraConfig = "Storage=volatile";
|
||||
test-support.displayManager.auto.user = "alice";
|
||||
|
||||
systemd.shutdown.test = pkgs.writeScript "test.shutdown" ''
|
||||
#!${pkgs.runtimeShell}
|
||||
PATH=${
|
||||
lib.makeBinPath (
|
||||
with pkgs;
|
||||
[
|
||||
util-linux
|
||||
coreutils
|
||||
]
|
||||
)
|
||||
}
|
||||
mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared
|
||||
touch /tmp/shared/shutdown-test
|
||||
umount /tmp/shared
|
||||
'';
|
||||
systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/test".source =
|
||||
pkgs.writeShellScript "test.shutdown" ''
|
||||
PATH=${
|
||||
lib.makeBinPath (
|
||||
with pkgs;
|
||||
[
|
||||
util-linux
|
||||
coreutils
|
||||
]
|
||||
)
|
||||
}
|
||||
mkdir -p /tmp/shared
|
||||
mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared
|
||||
touch /tmp/shared/shutdown-test
|
||||
umount /tmp/shared
|
||||
'';
|
||||
systemd.shutdownRamfs.storePaths = [ "${pkgs.util-linux}/bin" ];
|
||||
|
||||
systemd.services.oncalendar-test = {
|
||||
description = "calendar test";
|
||||
@@ -147,7 +149,7 @@
|
||||
|
||||
subprocess.check_call(
|
||||
[
|
||||
"qemu-img",
|
||||
"${nodes.machine.virtualisation.qemu.package}/bin/qemu-img",
|
||||
"convert",
|
||||
"-O",
|
||||
"raw",
|
||||
|
||||
@@ -31,11 +31,11 @@ let
|
||||
in
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "nano";
|
||||
version = "9.0";
|
||||
version = "9.1";
|
||||
|
||||
src = fetchurl {
|
||||
url = "mirror://gnu/nano/${pname}-${version}.tar.xz";
|
||||
hash = "sha256-nzhDdLSWEQoltzrVpf67OEeDxuMYizcGP2d6yQgBP94=";
|
||||
url = "mirror://gnu/nano/nano-${version}.tar.xz";
|
||||
hash = "sha256-X0d2QnTLdTI0nOCqIOwQ8ejoUabp+j62aBLEPRltsEI=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ texinfo ] ++ lib.optional enableNls gettext;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "ruff";
|
||||
version = "0.15.18";
|
||||
version = "0.15.20";
|
||||
|
||||
__structuredAttrs = true;
|
||||
|
||||
@@ -24,12 +24,12 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
owner = "astral-sh";
|
||||
repo = "ruff";
|
||||
tag = finalAttrs.version;
|
||||
hash = "sha256-DH00tENXdCdNcGPXPGzZsU3RVYQ0VBe1QLvbgEg/G6k=";
|
||||
hash = "sha256-8PFMGKG15kWBpG4YXg37940WtSe/e5pQDqIe3iJRh5A=";
|
||||
};
|
||||
|
||||
cargoBuildFlags = [ "--package=ruff" ];
|
||||
|
||||
cargoHash = "sha256-RmK14NMPbhoRVLwIF5GXdGQg2AnMH20JGx7XF4HO+wY=";
|
||||
cargoHash = "sha256-Bf6nsUnNMYapP0YN0SBkTPoP1czmj35tPwN1awyKhUw=";
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "ty";
|
||||
version = "0.0.52";
|
||||
version = "0.0.54";
|
||||
__structuredAttrs = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
@@ -25,7 +25,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
repo = "ty";
|
||||
tag = finalAttrs.version;
|
||||
fetchSubmodules = true;
|
||||
hash = "sha256-p9fTzQ4DYvnwrtLdpSTekBV4ZbR1KETR8dfsbp8CDpo=";
|
||||
hash = "sha256-hbVH0dCUHkWKD9IG/CYhYI4TfLgpk++tPOkCD36eVSg=";
|
||||
};
|
||||
|
||||
# For Darwin platforms, remove the integration test for file notifications,
|
||||
@@ -39,7 +39,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
|
||||
cargoBuildFlags = [ "--package=ty" ];
|
||||
|
||||
cargoHash = "sha256-NUIdYOeyRsR/ZQueEXshYdWTnSeQiRjZRRi2ag8Dm48=";
|
||||
cargoHash = "sha256-Bf6nsUnNMYapP0YN0SBkTPoP1czmj35tPwN1awyKhUw=";
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
buildInputs = [ rust-jemalloc-sys ];
|
||||
|
||||
@@ -99,6 +99,13 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
excludes = [ "networking/httpd_ratelimit_cgi.c" ]; # New since release.
|
||||
hash = "sha256-Msm9sDZrVx7ofunnvnTS73SPKUUpR3Tv5xZ/wBd+rts=";
|
||||
})
|
||||
# tar: only strip unsafe components from hardlinks, not symlinks
|
||||
# fix issue introduced by the previous patch (CVE-2026-26157_CVE-2026-26158.patch)
|
||||
(fetchpatch {
|
||||
name = "CVE-2026-26157_CVE-2026-26158-2.patch";
|
||||
url = "https://github.com/vda-linux/busybox_mirror/commit/599f5dd8fac390c18b79cba4c14c334957605dae.patch";
|
||||
hash = "sha256-go/KHSsuMSm21nC0yvKEtAQs8Jnjjqdcs5i8RWBGwT4=";
|
||||
})
|
||||
# syslogd: fix writing to local log file
|
||||
# https://lists.busybox.net/pipermail/busybox/2024-October/090969.html
|
||||
(fetchpatch {
|
||||
@@ -263,12 +270,6 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
# Relies on suid/guid bits
|
||||
skip-testcase cpio.tests "cpio restores suid/sgid bits"
|
||||
|
||||
# Weird failures, looks related to our sandbox
|
||||
skip-testcase tar.tests "tar does not extract into symlinks"
|
||||
skip-testcase tar.tests "tar -k does not extract into symlinks"
|
||||
skip-testcase tar.tests "tar Symlink attack: create symlink and then write through it"
|
||||
skip-testcase tar.tests "tar Symlinks and hardlinks coexist"
|
||||
|
||||
popd
|
||||
'';
|
||||
});
|
||||
|
||||
@@ -30,18 +30,18 @@
|
||||
"lts": true
|
||||
},
|
||||
"6.18": {
|
||||
"version": "6.18.36",
|
||||
"hash": "sha256:0kn4r43lnd5nb5c298b30030qyaxv05s7k40n9si1j3iyk4qdazv",
|
||||
"version": "6.18.37",
|
||||
"hash": "sha256:0maj2ap1m09bxl6a3g9wc65h9sdr6y8rwc5qcqlbavb4wq0d4g58",
|
||||
"lts": true
|
||||
},
|
||||
"7.0": {
|
||||
"version": "7.0.13",
|
||||
"hash": "sha256:04wrz38ldls7pv1yxa1m7p2hqn1731l93xnz93fs7b0nyz8fv09w",
|
||||
"version": "7.0.14",
|
||||
"hash": "sha256:160ggaq9rh50a39gz02fpia8maq85bwxhqlwsc03yafjhjvrk6fy",
|
||||
"lts": false
|
||||
},
|
||||
"7.1": {
|
||||
"version": "7.1.1",
|
||||
"hash": "sha256:0z8x6wafxzc5vkim9jh8wpycdkk9y5bpxgsirmdpyznw84szl5aj",
|
||||
"version": "7.1.2",
|
||||
"hash": "sha256:0gw8nnq6nix9xk2dhb1jwmhnqjayrn3bn2akzg4lgqkvfa9qq69p",
|
||||
"lts": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,6 +703,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
withMachined
|
||||
withNetworkd
|
||||
withNspawn
|
||||
withRepart
|
||||
withPortabled
|
||||
withSysupdate
|
||||
withTimedated
|
||||
|
||||
@@ -196,7 +196,6 @@ with pkgs;
|
||||
inherit (nix-update) nix-update-script;
|
||||
|
||||
nixos-test-driver = pkgs.python3Packages.callPackage ../../nixos/lib/test-driver {
|
||||
qemu_pkg = pkgs.qemu;
|
||||
imagemagick_light = pkgs.imagemagick_light.override { inherit (pkgs) libtiff; };
|
||||
tesseract4 = pkgs.tesseract4.override { enableLanguages = [ "eng" ]; };
|
||||
# We want `pkgs.systemd`, *not* `python3Packages.system`.
|
||||
|
||||
Reference in New Issue
Block a user