1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-09 07:53:55 +02:00

Tagging release 2.26.2

-----BEGIN PGP SIGNATURE-----
 
 iQFHBAABCAAxFiEEtUHVUwEnDgvPFcpdgXC0cm1xmN4FAmetA5oTHGVkb2xzdHJh
 QGdtYWlsLmNvbQAKCRCBcLRybXGY3g2pB/9JAFyjmaXuccbMTO/6x9qwsWuuXNLk
 OQWzfbdUekvsihZZSFZg1r7KqqXHCi64f0nxLPsJ/0oeDWZktJ5KnbV630nuUlDj
 ulLCpKdvhWFa8dVx9LiziGwQw4KLx8PjOfwThtQ4DqCWxWEmu6lKkijag9cE+ai4
 3mw9YtUjBRxlXyhYLzWz3whLbv37c/m+R8iGS8xm8W260pmei6D0beOIPdfXYBQF
 PzPlPORyI08A06uqyA3z7bTxzmSMnzvu0QInCPCKSHzFUnTZPHUYuYStFl28NrZS
 fXKK59L0G7QEfdTRAmqQkdHdtPj2RlYFiMN0kQiNLflvKfGGWdi/kvdx
 =rRix
 -----END PGP SIGNATURE-----

Merge tag '2.26.2' into sync-2.26.2

Tagging release 2.26.2
This commit is contained in:
Eelco Dolstra 2025-02-18 19:56:22 +01:00
commit 4055239936
1395 changed files with 24694 additions and 16040 deletions

View file

@ -4,8 +4,11 @@
nodes.machine = {
virtualisation.writableStore = true;
# TODO add a test without allowed-users setting. allowed-users is uncommon among NixOS users.
nix.settings.allowed-users = ["alice" "bob"];
nix.settings.trusted-users = ["alice"];
nix.settings.allowed-users = [
"alice"
"bob"
];
nix.settings.trusted-users = [ "alice" ];
users.users.alice.isNormalUser = true;
users.users.bob.isNormalUser = true;
@ -13,80 +16,80 @@
};
testScript =
let
pathFour = "/nix/store/20xfy868aiic0r0flgzq4n5dq1yvmxkn-four";
in
''
machine.wait_for_unit("multi-user.target")
machine.succeed("""
exec 1>&2
echo kSELDhobKaF8/VdxIxdP7EQe+Q > one
diff $(nix store add-file one) one
""")
machine.succeed("""
su --login alice -c '
set -x
cd ~
echo ehHtmfuULXYyBV6NBk6QUi8iE0 > two
ls
diff $(echo $(nix store add-file two)) two' 1>&2
""")
machine.succeed("""
su --login bob -c '
set -x
cd ~
echo 0Jw8RNp7cK0W2AdNbcquofcOVk > three
diff $(nix store add-file three) three
' 1>&2
""")
# We're going to check that a path is not created
machine.succeed("""
! [[ -e ${pathFour} ]]
""")
machine.succeed("""
su --login mallory -c '
set -x
cd ~
echo 5mgtDj0ohrWkT50TLR0f4tIIxY > four;
(! nix store add-file four 2>&1) | grep -F "cannot open connection to remote store"
(! nix store add-file four 2>&1) | grep -F "Connection reset by peer"
! [[ -e ${pathFour} ]]
' 1>&2
""")
# Check that the file _can_ be added, and matches the expected path we were checking
machine.succeed("""
exec 1>&2
echo 5mgtDj0ohrWkT50TLR0f4tIIxY > four
four="$(nix store add-file four)"
diff $four four
diff <(echo $four) <(echo ${pathFour})
""")
machine.succeed("""
su --login alice -c 'nix-store --verify --repair'
""")
machine.succeed("""
set -x
su --login bob -c '(! nix-store --verify --repair 2>&1)' | tee diag 1>&2
grep -F "you are not privileged to repair paths" diag
""")
machine.succeed("""
set -x
su --login mallory -c '
nix-store --generate-binary-cache-key cache1.example.org sk1 pk1
(! nix store sign --key-file sk1 ${pathFour} 2>&1)' | tee diag 1>&2
grep -F "cannot open connection to remote store 'daemon'" diag
""")
machine.succeed("""
let
pathFour = "/nix/store/20xfy868aiic0r0flgzq4n5dq1yvmxkn-four";
in
''
machine.wait_for_unit("multi-user.target")
machine.succeed("""
exec 1>&2
echo kSELDhobKaF8/VdxIxdP7EQe+Q > one
diff $(nix store add-file one) one
""")
machine.succeed("""
su --login alice -c '
set -x
cd ~
echo ehHtmfuULXYyBV6NBk6QUi8iE0 > two
ls
diff $(echo $(nix store add-file two)) two' 1>&2
""")
machine.succeed("""
su --login bob -c '
nix-store --generate-binary-cache-key cache1.example.org sk1 pk1
nix store sign --key-file sk1 ${pathFour}
'
""")
'';
set -x
cd ~
echo 0Jw8RNp7cK0W2AdNbcquofcOVk > three
diff $(nix store add-file three) three
' 1>&2
""")
# We're going to check that a path is not created
machine.succeed("""
! [[ -e ${pathFour} ]]
""")
machine.succeed("""
su --login mallory -c '
set -x
cd ~
echo 5mgtDj0ohrWkT50TLR0f4tIIxY > four;
(! nix store add-file four 2>&1) | grep -F "cannot open connection to remote store"
(! nix store add-file four 2>&1) | grep -F "Connection reset by peer"
! [[ -e ${pathFour} ]]
' 1>&2
""")
# Check that the file _can_ be added, and matches the expected path we were checking
machine.succeed("""
exec 1>&2
echo 5mgtDj0ohrWkT50TLR0f4tIIxY > four
four="$(nix store add-file four)"
diff $four four
diff <(echo $four) <(echo ${pathFour})
""")
machine.succeed("""
su --login alice -c 'nix-store --verify --repair'
""")
machine.succeed("""
set -x
su --login bob -c '(! nix-store --verify --repair 2>&1)' | tee diag 1>&2
grep -F "you are not privileged to repair paths" diag
""")
machine.succeed("""
set -x
su --login mallory -c '
nix-store --generate-binary-cache-key cache1.example.org sk1 pk1
(! nix store sign --key-file sk1 ${pathFour} 2>&1)' | tee diag 1>&2
grep -F "cannot open connection to remote store 'daemon'" diag
""")
machine.succeed("""
su --login bob -c '
nix-store --generate-binary-cache-key cache1.example.org sk1 pk1
nix store sign --key-file sk1 ${pathFour}
'
""")
'';
}

View file

@ -27,12 +27,15 @@ let
# domain socket.
# Compiled statically so that we can easily send it to the VM and use it
# inside the build sandbox.
sender = pkgs.runCommandWith {
name = "sender";
stdenv = pkgs.pkgsStatic.stdenv;
} ''
$CC -static -o $out ${./sender.c}
'';
sender =
pkgs.runCommandWith
{
name = "sender";
stdenv = pkgs.pkgsStatic.stdenv;
}
''
$CC -static -o $out ${./sender.c}
'';
# Okay, so we have a file descriptor shipped out of the FOD now. But the
# Nix store is read-only, right? .. Well, yeah. But this file descriptor
@ -47,44 +50,57 @@ in
name = "ca-fd-leak";
nodes.machine =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
{
config,
lib,
pkgs,
...
}:
{
virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
virtualisation.additionalPaths = [ pkgs.busybox-sandbox-shell sender smuggler pkgs.socat ];
virtualisation.additionalPaths = [
pkgs.busybox-sandbox-shell
sender
smuggler
pkgs.socat
];
};
testScript = { nodes }: ''
start_all()
testScript =
{ nodes }:
''
start_all()
machine.succeed("echo hello")
# Start the smuggler server
machine.succeed("${smuggler}/bin/smuggler ${socketName} >&2 &")
machine.succeed("echo hello")
# Start the smuggler server
machine.succeed("${smuggler}/bin/smuggler ${socketName} >&2 &")
# Build the smuggled derivation.
# This will connect to the smuggler server and send it the file descriptor
machine.succeed(r"""
nix-build -E '
builtins.derivation {
name = "smuggled";
system = builtins.currentSystem;
# look ma, no tricks!
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = builtins.hashString "sha256" "hello, world\n";
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ "-c" "echo \"hello, world\" > $out; ''${${sender}} ${socketName}" ];
}'
""".strip())
# Build the smuggled derivation.
# This will connect to the smuggler server and send it the file descriptor
machine.succeed(r"""
nix-build -E '
builtins.derivation {
name = "smuggled";
system = builtins.currentSystem;
# look ma, no tricks!
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = builtins.hashString "sha256" "hello, world\n";
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ "-c" "echo \"hello, world\" > $out; ''${${sender}} ${socketName}" ];
}'
""".strip())
# Tell the smuggler server that we're done
machine.execute("echo done | ${pkgs.socat}/bin/socat - ABSTRACT-CONNECT:${socketName}")
# Tell the smuggler server that we're done
machine.execute("echo done | ${pkgs.socat}/bin/socat - ABSTRACT-CONNECT:${socketName}")
# Check that the file was not modified
machine.succeed(r"""
cat ./result
test "$(cat ./result)" = "hello, world"
""".strip())
'';
# Check that the file was not modified
machine.succeed(r"""
cat ./result
test "$(cat ./result)" = "hello, world"
""".strip())
'';
}

View file

@ -49,8 +49,8 @@ int main(int argc, char **argv) {
msg.msg_controllen = CMSG_SPACE(sizeof(int));
// Write a single null byte too.
msg.msg_iov = malloc(sizeof(struct iovec));
msg.msg_iov[0].iov_base = "";
msg.msg_iov = (struct iovec*) malloc(sizeof(struct iovec));
msg.msg_iov[0].iov_base = (void*) "";
msg.msg_iov[0].iov_len = 1;
msg.msg_iovlen = 1;

View file

@ -16,7 +16,7 @@ int main(int argc, char **argv) {
struct sockaddr_un data;
data.sun_family = AF_UNIX;
data.sun_path[0] = 0;
strcpy(data.sun_path + 1, argv[1]);
strncpy(data.sun_path + 1, argv[1], sizeof(data.sun_path) - 1);
int res = bind(sock, (const struct sockaddr *)&data,
offsetof(struct sockaddr_un, sun_path)
+ strlen(argv[1])
@ -57,10 +57,11 @@ int main(int argc, char **argv) {
// Wait for a second connection, which will tell us that the build is
// done
a = accept(sock, 0, 0);
if (a < 0) perror("accept");
fprintf(stderr, "%s\n", "Got a second connection, rewriting the file");
// Write a new content to the file
if (ftruncate(smuggling_fd, 0)) perror("ftruncate");
char * new_content = "Pwned\n";
const char * new_content = "Pwned\n";
int written_bytes = write(smuggling_fd, new_content, strlen(new_content));
if (written_bytes != strlen(new_content)) perror("write");
}

View file

@ -0,0 +1,41 @@
{ nixpkgs, ... }:
{
name = "cgroups";
nodes = {
host =
{ config, pkgs, ... }:
{
virtualisation.additionalPaths = [ pkgs.stdenvNoCC ];
nix.extraOptions = ''
extra-experimental-features = auto-allocate-uids cgroups
extra-system-features = uid-range
'';
nix.settings.use-cgroups = true;
nix.nixPath = [ "nixpkgs=${nixpkgs}" ];
};
};
testScript =
{ nodes }:
''
start_all()
host.wait_for_unit("multi-user.target")
# Start build in background
host.execute("NIX_REMOTE=daemon nix build --auto-allocate-uids --file ${./hang.nix} >&2 &")
service = "/sys/fs/cgroup/system.slice/nix-daemon.service"
# Wait for cgroups to be created
host.succeed(f"until [ -e {service}/nix-daemon ]; do sleep 1; done", timeout=30)
host.succeed(f"until [ -e {service}/nix-build-uid-* ]; do sleep 1; done", timeout=30)
# Check that there aren't processes where there shouldn't be, and that there are where there should be
host.succeed(f'[ -z "$(cat {service}/cgroup.procs)" ]')
host.succeed(f'[ -n "$(cat {service}/nix-daemon/cgroup.procs)" ]')
host.succeed(f'[ -n "$(cat {service}/nix-build-uid-*/cgroup.procs)" ]')
'';
}

View file

@ -0,0 +1,11 @@
{ }:
with import <nixpkgs> { };
runCommand "hang"
{
requiredSystemFeatures = "uid-range";
}
''
sleep infinity
''

View file

@ -0,0 +1,44 @@
{
lib,
config,
nixpkgs,
...
}:
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
pkgA = pkgs.hello;
pkgB = pkgs.cowsay;
in
{
name = "chroot-store";
nodes = {
machine =
{
config,
lib,
pkgs,
...
}:
{
virtualisation.writableStore = true;
virtualisation.additionalPaths = [ pkgA ];
environment.systemPackages = [ pkgB ];
};
};
testScript =
{ nodes }:
''
# fmt: off
start_all()
machine.succeed("nix copy --no-check-sigs --to /tmp/nix ${pkgA}")
machine.succeed("nix shell --store /tmp/nix ${pkgA} --command hello >&2")
# Test that /nix/store is available via an overlayfs mount.
machine.succeed("nix shell --store /tmp/nix ${pkgA} --command cowsay foo >&2")
'';
}

View file

@ -4,60 +4,67 @@
{
name = "containers";
nodes =
{
host =
{ config, lib, pkgs, nodes, ... }:
{ virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths =
[ pkgs.stdenvNoCC
(import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel
];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
nix.extraOptions =
''
extra-experimental-features = auto-allocate-uids cgroups
extra-system-features = uid-range
'';
nix.nixPath = [ "nixpkgs=${nixpkgs}" ];
};
};
nodes = {
host =
{
config,
lib,
pkgs,
nodes,
...
}:
{
virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [
pkgs.stdenvNoCC
(import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel
];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
nix.extraOptions = ''
extra-experimental-features = auto-allocate-uids cgroups
extra-system-features = uid-range
'';
nix.nixPath = [ "nixpkgs=${nixpkgs}" ];
};
};
testScript = { nodes }: ''
start_all()
testScript =
{ nodes }:
''
start_all()
host.succeed("nix --version >&2")
host.succeed("nix --version >&2")
# Test that 'id' gives the expected result in various configurations.
# Test that 'id' gives the expected result in various configurations.
# Existing UIDs, sandbox.
host.succeed("nix build --no-auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-1")
host.succeed("[[ $(cat ./result) = 'uid=1000(nixbld) gid=100(nixbld) groups=100(nixbld)' ]]")
# Existing UIDs, sandbox.
host.succeed("nix build --no-auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-1")
host.succeed("[[ $(cat ./result) = 'uid=1000(nixbld) gid=100(nixbld) groups=100(nixbld)' ]]")
# Existing UIDs, no sandbox.
host.succeed("nix build --no-auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-2")
host.succeed("[[ $(cat ./result) = 'uid=30001(nixbld1) gid=30000(nixbld) groups=30000(nixbld)' ]]")
# Existing UIDs, no sandbox.
host.succeed("nix build --no-auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-2")
host.succeed("[[ $(cat ./result) = 'uid=30001(nixbld1) gid=30000(nixbld) groups=30000(nixbld)' ]]")
# Auto-allocated UIDs, sandbox.
host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-3")
host.succeed("[[ $(cat ./result) = 'uid=1000(nixbld) gid=100(nixbld) groups=100(nixbld)' ]]")
# Auto-allocated UIDs, sandbox.
host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-3")
host.succeed("[[ $(cat ./result) = 'uid=1000(nixbld) gid=100(nixbld) groups=100(nixbld)' ]]")
# Auto-allocated UIDs, no sandbox.
host.succeed("nix build --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-4")
host.succeed("[[ $(cat ./result) = 'uid=872415232 gid=30000(nixbld) groups=30000(nixbld)' ]]")
# Auto-allocated UIDs, no sandbox.
host.succeed("nix build --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-4")
host.succeed("[[ $(cat ./result) = 'uid=872415232 gid=30000(nixbld) groups=30000(nixbld)' ]]")
# Auto-allocated UIDs, UID range, sandbox.
host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-5 --arg uidRange true")
host.succeed("[[ $(cat ./result) = 'uid=0(root) gid=0(root) groups=0(root)' ]]")
# Auto-allocated UIDs, UID range, sandbox.
host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-5 --arg uidRange true")
host.succeed("[[ $(cat ./result) = 'uid=0(root) gid=0(root) groups=0(root)' ]]")
# Auto-allocated UIDs, UID range, no sandbox.
host.fail("nix build --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-6 --arg uidRange true")
# Auto-allocated UIDs, UID range, no sandbox.
host.fail("nix build --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-6 --arg uidRange true")
# Run systemd-nspawn in a Nix build.
host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./systemd-nspawn.nix} --argstr nixpkgs ${nixpkgs}")
host.succeed("[[ $(cat ./result/msg) = 'Hello World' ]]")
'';
# Run systemd-nspawn in a Nix build.
host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./systemd-nspawn.nix} --argstr nixpkgs ${nixpkgs}")
host.succeed("[[ $(cat ./result/msg) = 'Hello World' ]]")
'';
}

View file

@ -1,8 +1,10 @@
{ name, uidRange ? false }:
{
name,
uidRange ? false,
}:
with import <nixpkgs> {};
with import <nixpkgs> { };
runCommand name
{ requiredSystemFeatures = if uidRange then ["uid-range"] else [];
}
"id; id > $out"
runCommand name {
requiredSystemFeatures = if uidRange then [ "uid-range" ] else [ ];
} "id; id > $out"

View file

@ -2,7 +2,8 @@
let
machine = { config, pkgs, ... }:
machine =
{ config, pkgs, ... }:
{
system.stateVersion = "22.05";
boot.isContainer = true;
@ -31,10 +32,12 @@ let
};
};
cfg = (import (nixpkgs + "/nixos/lib/eval-config.nix") {
modules = [ machine ];
system = "x86_64-linux";
});
cfg = (
import (nixpkgs + "/nixos/lib/eval-config.nix") {
modules = [ machine ];
system = "x86_64-linux";
}
);
config = cfg.config;
@ -43,7 +46,8 @@ in
with cfg._module.args.pkgs;
runCommand "test"
{ buildInputs = [ config.system.path ];
{
buildInputs = [ config.system.path ];
requiredSystemFeatures = [ "uid-range" ];
toplevel = config.system.build.toplevel;
}

View file

@ -1,23 +1,42 @@
{ lib, nixpkgs, nixpkgsFor, self }:
{
lib,
nixpkgs,
nixpkgsFor,
self,
}:
let
nixos-lib = import (nixpkgs + "/nixos/lib") { };
noTests =
pkg:
pkg.overrideAttrs (
finalAttrs: prevAttrs: {
doCheck = false;
doInstallCheck = false;
}
);
# https://nixos.org/manual/nixos/unstable/index.html#sec-calling-nixos-tests
runNixOSTestFor = system: test:
runNixOSTestFor =
system: test:
(nixos-lib.runTest {
imports = [
test
# Add the quickBuild attribute to the check packages
./quick-build.nix
];
hostPkgs = nixpkgsFor.${system}.native;
defaults = {
nixpkgs.pkgs = nixpkgsFor.${system}.native;
nix.checkAllErrors = false;
# TODO: decide which packaging stage to use. `nix-cli` is efficient, but not the same as the user-facing `everything.nix` package (`default`). Perhaps a good compromise is `everything.nix` + `noTests` defined above?
nix.package = nixpkgsFor.${system}.native.nixComponents.nix-cli;
# Evaluate VMs faster
documentation.enable = false;
# this links against nix and might break with our git version.
system.tools.nixos-option.enable = false;
};
_module.args.nixpkgs = nixpkgs;
_module.args.system = system;
@ -26,42 +45,61 @@ let
# allow running tests against older nix versions via `nix eval --apply`
# Example:
# nix build "$(nix eval --raw --impure .#hydraJobs.tests.fetch-git --apply 't: (t.forNix "2.19.2").drvPath')^*"
forNix = nixVersion: runNixOSTestFor system {
imports = [test];
defaults.nixpkgs.overlays = [(curr: prev: {
nix = (builtins.getFlake "nix/${nixVersion}").packages.${system}.nix;
})];
};
forNix =
nixVersion:
runNixOSTestFor system {
imports = [ test ];
defaults.nixpkgs.overlays = [
(curr: prev: {
nix =
let
packages = (builtins.getFlake "nix/${nixVersion}").packages.${system};
in
packages.nix-cli or packages.nix;
})
];
};
};
# Checks that a NixOS configuration does not contain any references to our
# locally defined Nix version.
checkOverrideNixVersion = { pkgs, lib, ... }: {
# pkgs.nix: The new Nix in this repo
# We disallow it, to make sure we don't accidentally use it.
system.forbiddenDependenciesRegexes = [
(lib.strings.escapeRegex "nix-${pkgs.nix.version}")
];
};
checkOverrideNixVersion =
{ pkgs, lib, ... }:
{
# pkgs.nix: The new Nix in this repo
# We disallow it, to make sure we don't accidentally use it.
system.forbiddenDependenciesRegexes = [
(lib.strings.escapeRegex "nix-${pkgs.nix.version}")
];
};
otherNixes.nix_2_3.setNixPackage = { lib, pkgs, ... }: {
imports = [ checkOverrideNixVersion ];
nix.package = lib.mkForce pkgs.nixVersions.nix_2_3;
};
otherNixes.nix_2_3.setNixPackage =
{ lib, pkgs, ... }:
{
imports = [ checkOverrideNixVersion ];
nix.package = lib.mkForce pkgs.nixVersions.nix_2_3;
};
otherNixes.nix_2_13.setNixPackage = { lib, pkgs, ... }: {
imports = [ checkOverrideNixVersion ];
nix.package = lib.mkForce (
self.inputs.nixpkgs-23-11.legacyPackages.${pkgs.stdenv.hostPlatform.system}.nixVersions.nix_2_13.overrideAttrs (o: {
meta = o.meta // { knownVulnerabilities = []; };
})
);
};
otherNixes.nix_2_13.setNixPackage =
{ lib, pkgs, ... }:
{
imports = [ checkOverrideNixVersion ];
nix.package = lib.mkForce (
self.inputs.nixpkgs-23-11.legacyPackages.${pkgs.stdenv.hostPlatform.system}.nixVersions.nix_2_13.overrideAttrs
(o: {
meta = o.meta // {
knownVulnerabilities = [ ];
};
})
);
};
otherNixes.nix_2_18.setNixPackage = { lib, pkgs, ... }: {
imports = [ checkOverrideNixVersion ];
nix.package = lib.mkForce pkgs.nixVersions.nix_2_18;
};
otherNixes.nix_2_18.setNixPackage =
{ lib, pkgs, ... }:
{
imports = [ checkOverrideNixVersion ];
nix.package = lib.mkForce pkgs.nixVersions.nix_2_18;
};
in
@ -74,30 +112,37 @@ in
}
// lib.concatMapAttrs (
nixVersion: { setNixPackage, ... }:
nixVersion:
{ setNixPackage, ... }:
{
"remoteBuilds_remote_${nixVersion}" = runNixOSTestFor "x86_64-linux" {
name = "remoteBuilds_remote_${nixVersion}";
imports = [ ./remote-builds.nix ];
builders.config = { lib, pkgs, ... }: {
imports = [ setNixPackage ];
};
builders.config =
{ lib, pkgs, ... }:
{
imports = [ setNixPackage ];
};
};
"remoteBuilds_local_${nixVersion}" = runNixOSTestFor "x86_64-linux" {
name = "remoteBuilds_local_${nixVersion}";
imports = [ ./remote-builds.nix ];
nodes.client = { lib, pkgs, ... }: {
imports = [ setNixPackage ];
};
nodes.client =
{ lib, pkgs, ... }:
{
imports = [ setNixPackage ];
};
};
"remoteBuildsSshNg_remote_${nixVersion}" = runNixOSTestFor "x86_64-linux" {
name = "remoteBuildsSshNg_remote_${nixVersion}";
imports = [ ./remote-builds-ssh-ng.nix ];
builders.config = { lib, pkgs, ... }: {
imports = [ setNixPackage ];
};
builders.config =
{ lib, pkgs, ... }:
{
imports = [ setNixPackage ];
};
};
# FIXME: these tests don't work yet
@ -117,6 +162,8 @@ in
nix-copy = runNixOSTestFor "x86_64-linux" ./nix-copy.nix;
nix-docker = runNixOSTestFor "x86_64-linux" ./nix-docker.nix;
nssPreload = runNixOSTestFor "x86_64-linux" ./nss-preload.nix;
githubFlakes = runNixOSTestFor "x86_64-linux" ./github-flakes.nix;
@ -129,9 +176,7 @@ in
containers = runNixOSTestFor "x86_64-linux" ./containers/containers.nix;
setuid = lib.genAttrs
["x86_64-linux"]
(system: runNixOSTestFor system ./setuid.nix);
setuid = lib.genAttrs [ "x86_64-linux" ] (system: runNixOSTestFor system ./setuid.nix);
fetch-git = runNixOSTestFor "x86_64-linux" ./fetch-git;
@ -145,9 +190,17 @@ in
functional_root = runNixOSTestFor "x86_64-linux" ./functional/as-root.nix;
functional_symlinked-home = runNixOSTestFor "x86_64-linux" ./functional/symlinked-home.nix;
user-sandboxing = runNixOSTestFor "x86_64-linux" ./user-sandboxing;
s3-binary-cache-store = runNixOSTestFor "x86_64-linux" ./s3-binary-cache-store.nix;
fsync = runNixOSTestFor "x86_64-linux" ./fsync.nix;
cgroups = runNixOSTestFor "x86_64-linux" ./cgroups;
fetchurl = runNixOSTestFor "x86_64-linux" ./fetchurl.nix;
s3-binary-cache-store = runNixOSTestFor "x86_64-linux" ./s3-binary-cache-store.nix;
chrootStore = runNixOSTestFor "x86_64-linux" ./chroot-store.nix;
}

View file

@ -7,26 +7,27 @@
];
/*
Test cases
Test cases
Test cases are automatically imported from ./test-cases/{name}
Test cases are automatically imported from ./test-cases/{name}
The following is set up automatically for each test case:
- a repo with the {name} is created on the gitea server
- a repo with the {name} is created on the client
- the client repo is configured to push to the server repo
The following is set up automatically for each test case:
- a repo with the {name} is created on the gitea server
- a repo with the {name} is created on the client
- the client repo is configured to push to the server repo
Python variables:
- repo.path: the path to the directory of the client repo
- repo.git: the git command with the client repo as the working directory
- repo.remote: the url to the server repo
Python variables:
- repo.path: the path to the directory of the client repo
- repo.git: the git command with the client repo as the working directory
- repo.remote: the url to the server repo
*/
testCases =
map
(testCaseName: {...}: {
testCases = map (
testCaseName:
{ ... }:
{
imports = [ (./test-cases + "/${testCaseName}") ];
# ensures tests are named like their directories they are defined in
name = testCaseName;
})
(lib.attrNames (builtins.readDir ./test-cases));
}
) (lib.attrNames (builtins.readDir ./test-cases));
}

View file

@ -5,7 +5,8 @@
script = ''
# add a file to the repo
client.succeed(f"""
echo ${config.name /* to make the git tree and store path unique */} > {repo.path}/test-case \
echo ${config.name # to make the git tree and store path unique
} > {repo.path}/test-case \
&& echo lutyabrook > {repo.path}/new-york-state \
&& {repo.git} add test-case new-york-state \
&& {repo.git} commit -m 'commit1'

View file

@ -4,7 +4,8 @@
script = ''
# add a file to the repo
client.succeed(f"""
echo ${config.name /* to make the git tree and store path unique */} > {repo.path}/test-case \
echo ${config.name # to make the git tree and store path unique
} > {repo.path}/test-case \
&& echo chiang-mai > {repo.path}/thailand \
&& {repo.git} add test-case thailand \
&& {repo.git} commit -m 'commit1'

View file

@ -4,7 +4,8 @@
script = ''
# add a file to the repo
client.succeed(f"""
echo ${config.name /* to make the git tree and store path unique */} > {repo.path}/test-case \
echo ${config.name # to make the git tree and store path unique
} > {repo.path}/test-case \
&& echo chiang-mai > {repo.path}/thailand \
&& {repo.git} add test-case thailand \
&& {repo.git} commit -m 'commit1'

View file

@ -8,25 +8,27 @@ let
boolPyLiteral = b: if b then "True" else "False";
testCaseExtension = { config, ... }: {
options = {
repo.enable = mkOption {
type = types.bool;
default = true;
description = "Whether to provide a repo variable - automatic repo creation.";
testCaseExtension =
{ config, ... }:
{
options = {
repo.enable = mkOption {
type = types.bool;
default = true;
description = "Whether to provide a repo variable - automatic repo creation.";
};
repo.private = mkOption {
type = types.bool;
default = false;
description = "Whether the repo should be private.";
};
};
repo.private = mkOption {
type = types.bool;
default = false;
description = "Whether the repo should be private.";
config = mkIf config.repo.enable {
setupScript = ''
repo = Repo("${config.name}", private=${boolPyLiteral config.repo.private})
'';
};
};
config = mkIf config.repo.enable {
setupScript = ''
repo = Repo("${config.name}", private=${boolPyLiteral config.repo.private})
'';
};
};
in
{
options = {

View file

@ -1,4 +1,11 @@
{ lib, nixpkgs, system, pkgs, ... }: let
{
lib,
nixpkgs,
system,
pkgs,
...
}:
let
clientPrivateKey = pkgs.writeText "id_ed25519" ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
@ -9,41 +16,52 @@
-----END OPENSSH PRIVATE KEY-----
'';
clientPublicKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFt5a8eH8BYZYjoQhzXGVKKHJe1pw1D0p7O2Vb9VTLzB";
clientPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFt5a8eH8BYZYjoQhzXGVKKHJe1pw1D0p7O2Vb9VTLzB";
in {
in
{
imports = [
../testsupport/setup.nix
../testsupport/gitea-repo.nix
];
nodes = {
gitea = { pkgs, ... }: {
services.gitea.enable = true;
services.gitea.settings.service.DISABLE_REGISTRATION = true;
services.gitea.settings.log.LEVEL = "Info";
services.gitea.settings.database.LOG_SQL = false;
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 3000 ];
environment.systemPackages = [ pkgs.git pkgs.gitea ];
users.users.root.openssh.authorizedKeys.keys = [clientPublicKey];
# TODO: remove this after updating to nixos-23.11
nixpkgs.pkgs = lib.mkForce (import nixpkgs {
inherit system;
config.permittedInsecurePackages = [
"gitea-1.19.4"
gitea =
{ pkgs, ... }:
{
services.gitea.enable = true;
services.gitea.settings.service.DISABLE_REGISTRATION = true;
services.gitea.settings.log.LEVEL = "Info";
services.gitea.settings.database.LOG_SQL = false;
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 3000 ];
environment.systemPackages = [
pkgs.git
pkgs.gitea
];
});
};
client = { pkgs, ... }: {
environment.systemPackages = [ pkgs.git ];
};
};
defaults = { pkgs, ... }: {
environment.systemPackages = [ pkgs.jq ];
users.users.root.openssh.authorizedKeys.keys = [ clientPublicKey ];
# TODO: remove this after updating to nixos-23.11
nixpkgs.pkgs = lib.mkForce (
import nixpkgs {
inherit system;
config.permittedInsecurePackages = [
"gitea-1.19.4"
];
}
);
};
client =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.git ];
};
};
defaults =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.jq ];
};
setupScript = ''
import shlex

View file

@ -1,11 +1,16 @@
{ lib, config, extendModules, ... }:
{
lib,
config,
extendModules,
...
}:
let
inherit (lib)
mkOption
types
;
indent = lib.replaceStrings ["\n"] ["\n "];
indent = lib.replaceStrings [ "\n" ] [ "\n " ];
execTestCase = testCase: ''
@ -35,37 +40,39 @@ in
description = ''
The test cases. See `testScript`.
'';
type = types.listOf (types.submodule {
options.name = mkOption {
type = types.str;
description = ''
The name of the test case.
type = types.listOf (
types.submodule {
options.name = mkOption {
type = types.str;
description = ''
The name of the test case.
A repository with that name will be set up on the gitea server and locally.
'';
};
options.description = mkOption {
type = types.str;
description = ''
A description of the test case.
'';
};
options.setupScript = mkOption {
type = types.lines;
description = ''
Python code that runs before the test case.
'';
default = "";
};
options.script = mkOption {
type = types.lines;
description = ''
Python code that runs the test.
A repository with that name will be set up on the gitea server and locally.
'';
};
options.description = mkOption {
type = types.str;
description = ''
A description of the test case.
'';
};
options.setupScript = mkOption {
type = types.lines;
description = ''
Python code that runs before the test case.
'';
default = "";
};
options.script = mkOption {
type = types.lines;
description = ''
Python code that runs the test.
Variables defined by the global `setupScript`, as well as `testCases.*.setupScript` will be available here.
'';
};
});
Variables defined by the global `setupScript`, as well as `testCases.*.setupScript` will be available here.
'';
};
}
);
};
};
@ -75,8 +82,7 @@ in
_NIX_FORCE_HTTP = "1";
};
};
setupScript = ''
'';
setupScript = '''';
testScript = ''
start_all();

View file

@ -5,16 +5,20 @@
let
makeTlsCert = name: pkgs.runCommand name {
nativeBuildInputs = with pkgs; [ openssl ];
} ''
mkdir -p $out
openssl req -x509 \
-subj '/CN=${name}/' -days 49710 \
-addext 'subjectAltName = DNS:${name}' \
-keyout "$out/key.pem" -newkey ed25519 \
-out "$out/cert.pem" -noenc
'';
makeTlsCert =
name:
pkgs.runCommand name
{
nativeBuildInputs = with pkgs; [ openssl ];
}
''
mkdir -p $out
openssl req -x509 \
-subj '/CN=${name}/' -days 49710 \
-addext 'subjectAltName = DNS:${name}' \
-keyout "$out/key.pem" -newkey ed25519 \
-out "$out/cert.pem" -noenc
'';
goodCert = makeTlsCert "good";
badCert = makeTlsCert "bad";
@ -22,42 +26,45 @@ let
in
{
name = "nss-preload";
name = "fetchurl";
nodes = {
machine = { pkgs, ... }: {
services.nginx = {
enable = true;
machine =
{ pkgs, ... }:
{
services.nginx = {
enable = true;
virtualHosts."good" = {
addSSL = true;
sslCertificate = "${goodCert}/cert.pem";
sslCertificateKey = "${goodCert}/key.pem";
root = pkgs.runCommand "nginx-root" {} ''
mkdir "$out"
echo 'hello world' > "$out/index.html"
'';
virtualHosts."good" = {
addSSL = true;
sslCertificate = "${goodCert}/cert.pem";
sslCertificateKey = "${goodCert}/key.pem";
root = pkgs.runCommand "nginx-root" { } ''
mkdir "$out"
echo 'hello world' > "$out/index.html"
'';
};
virtualHosts."bad" = {
addSSL = true;
sslCertificate = "${badCert}/cert.pem";
sslCertificateKey = "${badCert}/key.pem";
root = pkgs.runCommand "nginx-root" { } ''
mkdir "$out"
echo 'foobar' > "$out/index.html"
'';
};
};
virtualHosts."bad" = {
addSSL = true;
sslCertificate = "${badCert}/cert.pem";
sslCertificateKey = "${badCert}/key.pem";
root = pkgs.runCommand "nginx-root" {} ''
mkdir "$out"
echo 'foobar' > "$out/index.html"
'';
};
security.pki.certificateFiles = [ "${goodCert}/cert.pem" ];
networking.hosts."127.0.0.1" = [
"good"
"bad"
];
virtualisation.writableStore = true;
};
security.pki.certificateFiles = [ "${goodCert}/cert.pem" ];
networking.hosts."127.0.0.1" = [ "good" "bad" ];
virtualisation.writableStore = true;
nix.settings.experimental-features = "nix-command";
};
};
testScript = ''
@ -76,7 +83,7 @@ in
# Fetching from a server with an untrusted cert should fail.
err = machine.fail("nix build --no-substitute --expr 'import <nix/fetchurl.nix> { url = \"https://bad/index.html\"; hash = \"sha256-rsBwZF/lPuOzdjBZN2E08FjMM3JHyXit0Xi2zN+wAZ8=\"; }' 2>&1")
print(err)
assert "SSL certificate problem: self-signed certificate" in err
assert "SSL peer certificate or SSH remote key was not OK" in err
# Fetching from a server with a trusted cert should work via environment variable override.
machine.succeed("NIX_SSL_CERT_FILE=/tmp/cafile.pem nix build --no-substitute --expr 'import <nix/fetchurl.nix> { url = \"https://bad/index.html\"; hash = \"sha256-rsBwZF/lPuOzdjBZN2E08FjMM3JHyXit0Xi2zN+wAZ8=\"; }'")

56
tests/nixos/fsync.nix Normal file
View file

@ -0,0 +1,56 @@
{
lib,
config,
nixpkgs,
pkgs,
...
}:
let
pkg1 = pkgs.go;
in
{
name = "fsync";
nodes.machine =
{
config,
lib,
pkgs,
...
}:
{
virtualisation.emptyDiskImages = [ 1024 ];
environment.systemPackages = [ pkg1 ];
nix.settings.fsync-store-paths = true;
nix.settings.require-sigs = false;
boot.supportedFilesystems = [
"ext4"
"btrfs"
"xfs"
];
};
testScript =
{ nodes }:
''
# fmt: off
for fs in ("ext4", "btrfs", "xfs"):
machine.succeed("mkfs.{} {} /dev/vdb".format(fs, "-F" if fs == "ext4" else "-f"))
machine.succeed("mkdir -p /mnt")
machine.succeed("mount /dev/vdb /mnt")
machine.succeed("sync")
machine.succeed("nix copy --offline ${pkg1} --to /mnt")
machine.crash()
machine.start()
machine.wait_for_unit("multi-user.target")
machine.succeed("mkdir -p /mnt")
machine.succeed("mount /dev/vdb /mnt")
machine.succeed("nix path-info --offline --store /mnt ${pkg1}")
machine.succeed("nix store verify --all --store /mnt --no-trust")
machine.succeed("umount /dev/vdb")
'';
}

View file

@ -4,7 +4,9 @@
imports = [ ./common.nix ];
nodes.machine = {
users.users.alice = { isNormalUser = true; };
users.users.alice = {
isNormalUser = true;
};
nix.settings.trusted-users = [ "alice" ];
};
@ -15,4 +17,4 @@
su --login --command "run-test-suite" alice >&2
""")
'';
}
}

View file

@ -4,7 +4,9 @@
imports = [ ./common.nix ];
nodes.machine = {
users.users.alice = { isNormalUser = true; };
users.users.alice = {
isNormalUser = true;
};
};
testScript = ''

View file

@ -2,9 +2,11 @@
let
# FIXME (roberth) reference issue
inputDerivation = pkg: (pkg.overrideAttrs (o: {
disallowedReferences = [ ];
})).inputDerivation;
inputDerivation =
pkg:
(pkg.overrideAttrs (o: {
disallowedReferences = [ ];
})).inputDerivation;
in
{
@ -12,60 +14,63 @@ in
# we skip it to save time.
skipTypeCheck = true;
nodes.machine = { config, pkgs, ... }: {
nodes.machine =
{ config, pkgs, ... }:
{
virtualisation.writableStore = true;
system.extraDependencies = [
(inputDerivation config.nix.package)
];
virtualisation.writableStore = true;
system.extraDependencies = [
(inputDerivation config.nix.package)
];
nix.settings.substituters = lib.mkForce [];
nix.settings.substituters = lib.mkForce [ ];
environment.systemPackages = let
run-test-suite = pkgs.writeShellApplication {
name = "run-test-suite";
runtimeInputs = [ pkgs.gnumake pkgs.jq pkgs.git ];
text = ''
set -x
cat /proc/sys/fs/file-max
ulimit -Hn
ulimit -Sn
cd ~
cp -r ${pkgs.nix.overrideAttrs (o: {
name = "nix-configured-source";
outputs = [ "out" ];
separateDebugInfo = false;
disallowedReferences = [ ];
buildPhase = ":";
checkPhase = ":";
installPhase = ''
cp -r . $out
environment.systemPackages =
let
run-test-suite = pkgs.writeShellApplication {
name = "run-test-suite";
runtimeInputs = [
pkgs.meson
pkgs.ninja
pkgs.jq
pkgs.git
# Want to avoid `/run/current-system/sw/bin/bash` because we
# want a store path. Likewise for coreutils.
pkgs.bash
pkgs.coreutils
];
text = ''
set -x
cat /proc/sys/fs/file-max
ulimit -Hn
ulimit -Sn
cd ~
cp -r ${pkgs.nixComponents.nix-functional-tests.src} nix
chmod -R +w nix
chmod u+w nix/.version
echo ${pkgs.nixComponents.version} > nix/.version
export isTestOnNixOS=1
export NIX_REMOTE_=daemon
export NIX_REMOTE=daemon
export NIX_STORE=${builtins.storeDir}
meson setup nix/tests/functional build
cd build
meson test -j1 --print-errorlogs
'';
installCheckPhase = ":";
fixupPhase = ":";
doInstallCheck = true;
})} nix
chmod -R +w nix
cd nix
# Tests we don't need
echo >tests/functional/plugins/local.mk
sed -i tests/functional/local.mk \
-e 's!nix_tests += plugins\.sh!!' \
-e 's!nix_tests += test-libstoreconsumer\.sh!!' \
;
export isTestOnNixOS=1
export version=${config.nix.package.version}
export NIX_REMOTE_=daemon
export NIX_REMOTE=daemon
export NIX_STORE=${builtins.storeDir}
make -j1 installcheck --keep-going
'';
};
in [
run-test-suite
pkgs.git
];
};
};
in
[
run-test-suite
pkgs.git
];
};
}

View file

@ -0,0 +1,38 @@
/**
This test runs the functional tests on a NixOS system where the home directory
is symlinked to another location.
The purpose of this test is to find cases where Nix uses low-level operations
that don't support symlinks on paths that include them.
It is not a substitute for more intricate, use case-specific tests, but helps
catch common issues.
*/
# TODO: add symlinked tmpdir
{ ... }:
{
name = "functional-tests-on-nixos_user_symlinked-home";
imports = [ ./common.nix ];
nodes.machine = {
users.users.alice = {
isNormalUser = true;
};
};
testScript = ''
machine.wait_for_unit("multi-user.target")
with subtest("prepare symlinked home"):
machine.succeed("""
(
set -x
mv /home/alice /home/alice.real
ln -s alice.real /home/alice
) 1>&2
""")
machine.succeed("""
su --login --command "run-test-suite" alice >&2
""")
'';
}

View file

@ -6,64 +6,73 @@
config = {
name = lib.mkDefault "git-submodules";
nodes =
{
remote =
{ config, pkgs, ... }:
{
services.openssh.enable = true;
environment.systemPackages = [ pkgs.git ];
};
nodes = {
remote =
{ config, pkgs, ... }:
{
services.openssh.enable = true;
environment.systemPackages = [ pkgs.git ];
};
client =
{ config, lib, pkgs, ... }:
{
programs.ssh.extraConfig = "ConnectTimeout 30";
environment.systemPackages = [ pkgs.git ];
};
};
client =
{
config,
lib,
pkgs,
...
}:
{
programs.ssh.extraConfig = "ConnectTimeout 30";
environment.systemPackages = [ pkgs.git ];
};
};
testScript = { nodes }: ''
# fmt: off
import subprocess
testScript =
{ nodes }:
''
# fmt: off
import subprocess
start_all()
start_all()
# Create an SSH key on the client.
subprocess.run([
"${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
client.succeed("mkdir -p -m 700 /root/.ssh")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Create an SSH key on the client.
subprocess.run([
"${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
client.succeed("mkdir -p -m 700 /root/.ssh")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Install the SSH key on the builders.
client.wait_for_unit("network.target")
# Install the SSH key on the builders.
client.wait_for_unit("network-online.target")
remote.succeed("mkdir -p -m 700 /root/.ssh")
remote.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
remote.wait_for_unit("sshd")
client.succeed(f"ssh -o StrictHostKeyChecking=no {remote.name} 'echo hello world'")
remote.succeed("mkdir -p -m 700 /root/.ssh")
remote.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
remote.wait_for_unit("sshd")
remote.wait_for_unit("multi-user.target")
remote.wait_for_unit("network-online.target")
client.wait_for_unit("network-online.target")
client.succeed(f"ssh -o StrictHostKeyChecking=no {remote.name} 'echo hello world'")
remote.succeed("""
git init bar
git -C bar config user.email foobar@example.com
git -C bar config user.name Foobar
echo test >> bar/content
git -C bar add content
git -C bar commit -m 'Initial commit'
""")
remote.succeed("""
git init bar
git -C bar config user.email foobar@example.com
git -C bar config user.name Foobar
echo test >> bar/content
git -C bar add content
git -C bar commit -m 'Initial commit'
""")
client.succeed(f"""
git init foo
git -C foo config user.email foobar@example.com
git -C foo config user.name Foobar
git -C foo submodule add root@{remote.name}:/tmp/bar sub
git -C foo add sub
git -C foo commit -m 'Add submodule'
""")
client.succeed(f"""
git init foo
git -C foo config user.email foobar@example.com
git -C foo config user.name Foobar
git -C foo submodule add root@{remote.name}:/tmp/bar sub
git -C foo add sub
git -C foo commit -m 'Add submodule'
""")
client.succeed("nix --flake-registry \"\" flake prefetch 'git+file:///tmp/foo?submodules=1&ref=master'")
'';
client.succeed("nix --flake-registry \"\" flake prefetch 'git+file:///tmp/foo?submodules=1&ref=master'")
'';
};
}

View file

@ -1,21 +1,25 @@
{ lib, config, nixpkgs, ... }:
{
lib,
config,
nixpkgs,
...
}:
let
pkgs = config.nodes.client.nixpkgs.pkgs;
# Generate a fake root CA and a fake api.github.com / github.com / channels.nixos.org certificate.
cert = pkgs.runCommand "cert" { nativeBuildInputs = [ pkgs.openssl ]; }
''
mkdir -p $out
cert = pkgs.runCommand "cert" { nativeBuildInputs = [ pkgs.openssl ]; } ''
mkdir -p $out
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 36500 -key ca.key \
-subj "/C=NL/ST=Denial/L=Springfield/O=Dis/CN=Root CA" -out $out/ca.crt
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 36500 -key ca.key \
-subj "/C=NL/ST=Denial/L=Springfield/O=Dis/CN=Root CA" -out $out/ca.crt
openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \
-subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=github.com" -out server.csr
openssl x509 -req -extfile <(printf "subjectAltName=DNS:api.github.com,DNS:github.com,DNS:channels.nixos.org") \
-days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt
'';
openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \
-subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=github.com" -out server.csr
openssl x509 -req -extfile <(printf "subjectAltName=DNS:api.github.com,DNS:github.com,DNS:channels.nixos.org") \
-days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt
'';
registry = pkgs.writeTextFile {
name = "registry";
@ -53,159 +57,189 @@ let
private-flake-rev = "9f1dd0df5b54a7dc75b618034482ed42ce34383d";
private-flake-api = pkgs.runCommand "private-flake" {}
''
mkdir -p $out/{commits,tarball}
private-flake-api = pkgs.runCommand "private-flake" { } ''
mkdir -p $out/{commits,tarball}
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
echo '{"sha": "${private-flake-rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
echo '{"sha": "${private-flake-rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
# Setup tarball download via API
dir=private-flake
mkdir $dir
echo '{ outputs = {...}: {}; }' > $dir/flake.nix
tar cfz $out/tarball/${private-flake-rev} $dir --hard-dereference
'';
# Setup tarball download via API
dir=private-flake
mkdir $dir
echo '{ outputs = {...}: {}; }' > $dir/flake.nix
tar cfz $out/tarball/${private-flake-rev} $dir --hard-dereference
'';
nixpkgs-api = pkgs.runCommand "nixpkgs-flake" {}
''
mkdir -p $out/commits
nixpkgs-api = pkgs.runCommand "nixpkgs-flake" { } ''
mkdir -p $out/commits
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
echo '{"sha": "${nixpkgs.rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
'';
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
echo '{"sha": "${nixpkgs.rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
'';
archive = pkgs.runCommand "nixpkgs-flake" {}
''
mkdir -p $out/archive
archive = pkgs.runCommand "nixpkgs-flake" { } ''
mkdir -p $out/archive
dir=NixOS-nixpkgs-${nixpkgs.shortRev}
cp -prd ${nixpkgs} $dir
# Set the correct timestamp in the tarball.
find $dir -print0 | xargs -0 touch -h -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${builtins.substring 12 2 nixpkgs.lastModifiedDate} --
tar cfz $out/archive/${nixpkgs.rev}.tar.gz $dir --hard-dereference
'';
dir=NixOS-nixpkgs-${nixpkgs.shortRev}
cp -prd ${nixpkgs} $dir
# Set the correct timestamp in the tarball.
find $dir -print0 | xargs -0 touch -h -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${
builtins.substring 12 2 nixpkgs.lastModifiedDate
} --
tar cfz $out/archive/${nixpkgs.rev}.tar.gz $dir --hard-dereference
'';
in
{
name = "github-flakes";
nodes =
{
github =
{ config, pkgs, ... }:
{ networking.firewall.allowedTCPPorts = [ 80 443 ];
nodes = {
github =
{ config, pkgs, ... }:
{
networking.firewall.allowedTCPPorts = [
80
443
];
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
services.httpd.extraConfig = ''
ErrorLog syslog:local6
'';
services.httpd.virtualHosts."channels.nixos.org" =
{ forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs =
[ { urlPath = "/";
dir = registry;
}
];
};
services.httpd.virtualHosts."api.github.com" =
{ forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs =
[ { urlPath = "/repos/NixOS/nixpkgs";
dir = nixpkgs-api;
}
{ urlPath = "/repos/fancy-enterprise/private-flake";
dir = private-flake-api;
}
];
};
services.httpd.virtualHosts."github.com" =
{ forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs =
[ { urlPath = "/NixOS/nixpkgs";
dir = archive;
}
];
};
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
services.httpd.extraConfig = ''
ErrorLog syslog:local6
'';
services.httpd.virtualHosts."channels.nixos.org" = {
forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs = [
{
urlPath = "/";
dir = registry;
}
];
};
client =
{ config, lib, pkgs, nodes, ... }:
{ virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
networking.hosts.${(builtins.head nodes.github.networking.interfaces.eth1.ipv4.addresses).address} =
[ "channels.nixos.org" "api.github.com" "github.com" ];
security.pki.certificateFiles = [ "${cert}/ca.crt" ];
services.httpd.virtualHosts."api.github.com" = {
forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs = [
{
urlPath = "/repos/NixOS/nixpkgs";
dir = nixpkgs-api;
}
{
urlPath = "/repos/fancy-enterprise/private-flake";
dir = private-flake-api;
}
];
};
};
services.httpd.virtualHosts."github.com" = {
forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs = [
{
urlPath = "/NixOS/nixpkgs";
dir = archive;
}
];
};
};
testScript = { nodes }: ''
# fmt: off
import json
import time
client =
{
config,
lib,
pkgs,
nodes,
...
}:
{
virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [
pkgs.hello
pkgs.fuse
];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
networking.hosts.${(builtins.head nodes.github.networking.interfaces.eth1.ipv4.addresses).address} =
[
"channels.nixos.org"
"api.github.com"
"github.com"
];
security.pki.certificateFiles = [ "${cert}/ca.crt" ];
};
};
start_all()
testScript =
{ nodes }:
''
# fmt: off
import json
import time
def cat_log():
github.succeed("cat /var/log/httpd/*.log >&2")
start_all()
github.wait_for_unit("httpd.service")
def cat_log():
github.succeed("cat /var/log/httpd/*.log >&2")
client.succeed("curl -v https://github.com/ >&2")
out = client.succeed("nix registry list")
print(out)
assert "github:NixOS/nixpkgs" in out, "nixpkgs flake not found"
assert "github:fancy-enterprise/private-flake" in out, "private flake not found"
cat_log()
github.wait_for_unit("httpd.service")
github.wait_for_unit("network-online.target")
# If no github access token is provided, nix should use the public archive url...
out = client.succeed("nix flake metadata nixpkgs --json")
print(out)
info = json.loads(out)
assert info["revision"] == "${nixpkgs.rev}", f"revision mismatch: {info['revision']} != ${nixpkgs.rev}"
cat_log()
client.wait_for_unit("network-online.target")
client.succeed("curl -v https://github.com/ >&2")
out = client.succeed("nix registry list")
print(out)
assert "github:NixOS/nixpkgs" in out, "nixpkgs flake not found"
assert "github:fancy-enterprise/private-flake" in out, "private flake not found"
cat_log()
# ... otherwise it should use the API
out = client.succeed("nix flake metadata private-flake --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0")
print(out)
info = json.loads(out)
assert info["revision"] == "${private-flake-rev}", f"revision mismatch: {info['revision']} != ${private-flake-rev}"
cat_log()
# If no github access token is provided, nix should use the public archive url...
out = client.succeed("nix flake metadata nixpkgs --json")
print(out)
info = json.loads(out)
assert info["revision"] == "${nixpkgs.rev}", f"revision mismatch: {info['revision']} != ${nixpkgs.rev}"
cat_log()
client.succeed("nix registry pin nixpkgs")
client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2")
# ... otherwise it should use the API
out = client.succeed("nix flake metadata private-flake --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0")
print(out)
info = json.loads(out)
assert info["revision"] == "${private-flake-rev}", f"revision mismatch: {info['revision']} != ${private-flake-rev}"
assert info["fingerprint"]
cat_log()
# Test fetchTree on a github URL.
hash = client.succeed(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree {info['url']}).narHash'")
assert hash == info['locked']['narHash']
# Fetching with the resolved URL should produce the same result.
info2 = json.loads(client.succeed(f"nix flake metadata {info['url']} --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0"))
print(info["fingerprint"], info2["fingerprint"])
assert info["fingerprint"] == info2["fingerprint"], "fingerprint mismatch"
# Fetching without a narHash should succeed if trust-github is set and fail otherwise.
client.succeed(f"nix eval --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}'")
out = client.fail(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}' 2>&1")
assert "will not fetch unlocked input" in out, "--no-trust-tarballs-from-git-forges did not fail with the expected error"
client.succeed("nix registry pin nixpkgs")
client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2")
# Shut down the web server. The flake should be cached on the client.
github.succeed("systemctl stop httpd.service")
# Test fetchTree on a github URL.
hash = client.succeed(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree {info['url']}).narHash'")
assert hash == info['locked']['narHash']
info = json.loads(client.succeed("nix flake metadata nixpkgs --json"))
date = time.strftime("%Y%m%d%H%M%S", time.gmtime(info['lastModified']))
assert date == "${nixpkgs.lastModifiedDate}", "time mismatch"
# Fetching without a narHash should succeed if trust-github is set and fail otherwise.
client.succeed(f"nix eval --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}'")
out = client.fail(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}' 2>&1")
assert "will not fetch unlocked input" in out, "--no-trust-tarballs-from-git-forges did not fail with the expected error"
client.succeed("nix build nixpkgs#hello")
# Shut down the web server. The flake should be cached on the client.
github.succeed("systemctl stop httpd.service")
# The build shouldn't fail even with --tarball-ttl 0 (the server
# being down should not be a fatal error).
client.succeed("nix build nixpkgs#fuse --tarball-ttl 0")
'';
info = json.loads(client.succeed("nix flake metadata nixpkgs --json"))
date = time.strftime("%Y%m%d%H%M%S", time.gmtime(info['lastModified']))
assert date == "${nixpkgs.lastModifiedDate}", "time mismatch"
client.succeed("nix build nixpkgs#hello")
# The build shouldn't fail even with --tarball-ttl 0 (the server
# being down should not be a fatal error).
client.succeed("nix build nixpkgs#fuse --tarball-ttl 0")
'';
}

View file

@ -30,42 +30,45 @@ in
{
name = "gzip-content-encoding";
nodes =
{ machine =
nodes = {
machine =
{ config, pkgs, ... }:
{ networking.firewall.allowedTCPPorts = [ 80 ];
{
networking.firewall.allowedTCPPorts = [ 80 ];
services.nginx.enable = true;
services.nginx.virtualHosts."localhost" =
{ root = "${ztdCompressedFile}/share/";
# Make sure that nginx really tries to compress the
# file on the fly with no regard to size/mime.
# http://nginx.org/en/docs/http/ngx_http_gzip_module.html
extraConfig = ''
gzip on;
gzip_types *;
gzip_proxied any;
gzip_min_length 0;
'';
};
services.nginx.virtualHosts."localhost" = {
root = "${ztdCompressedFile}/share/";
# Make sure that nginx really tries to compress the
# file on the fly with no regard to size/mime.
# http://nginx.org/en/docs/http/ngx_http_gzip_module.html
extraConfig = ''
gzip on;
gzip_types *;
gzip_proxied any;
gzip_min_length 0;
'';
};
virtualisation.writableStore = true;
virtualisation.additionalPaths = with pkgs; [ file ];
nix.settings.substituters = lib.mkForce [ ];
};
};
};
# Check that when nix-prefetch-url is used with a zst tarball it does not get decompressed.
testScript = { nodes }: ''
# fmt: off
start_all()
testScript =
{ nodes }:
''
# fmt: off
start_all()
machine.wait_for_unit("nginx.service")
machine.succeed("""
# Make sure that the file is properly compressed as the test would be meaningless otherwise
curl --compressed -v http://localhost/archive |& tr -s ' ' |& grep --ignore-case 'content-encoding: gzip'
archive_path=$(nix-prefetch-url http://localhost/archive --print-path | tail -n1)
[[ $(${fileCmd} --brief --mime-type $archive_path) == "application/zstd" ]]
tar --zstd -xf $archive_path
""")
'';
machine.wait_for_unit("nginx.service")
machine.succeed("""
# Make sure that the file is properly compressed as the test would be meaningless otherwise
curl --compressed -v http://localhost/archive |& tr -s ' ' |& grep --ignore-case 'content-encoding: gzip'
archive_path=$(nix-prefetch-url http://localhost/archive --print-path | tail -n1)
[[ $(${fileCmd} --brief --mime-type $archive_path) == "application/zstd" ]]
tar --zstd -xf $archive_path
""")
'';
}

View file

@ -1,6 +1,11 @@
# Test nix-copy-closure.
{ lib, config, nixpkgs, ... }:
{
lib,
config,
nixpkgs,
...
}:
let
pkgs = config.nodes.client.nixpkgs.pkgs;
@ -10,71 +15,90 @@ let
pkgC = pkgs.hello;
pkgD = pkgs.tmux;
in {
in
{
name = "nix-copy-closure";
nodes =
{ client =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
virtualisation.additionalPaths = [ pkgA pkgD.drvPath ];
nix.settings.substituters = lib.mkForce [ ];
};
nodes = {
client =
{
config,
lib,
pkgs,
...
}:
{
virtualisation.writableStore = true;
virtualisation.additionalPaths = [
pkgA
pkgD.drvPath
];
nix.settings.substituters = lib.mkForce [ ];
};
server =
{ config, pkgs, ... }:
{ services.openssh.enable = true;
virtualisation.writableStore = true;
virtualisation.additionalPaths = [ pkgB pkgC ];
};
};
server =
{ config, pkgs, ... }:
{
services.openssh.enable = true;
virtualisation.writableStore = true;
virtualisation.additionalPaths = [
pkgB
pkgC
];
};
};
testScript = { nodes }: ''
# fmt: off
import subprocess
testScript =
{ nodes }:
''
# fmt: off
import subprocess
start_all()
start_all()
# Create an SSH key on the client.
subprocess.run([
"${pkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
# Create an SSH key on the client.
subprocess.run([
"${pkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
client.succeed("mkdir -m 700 /root/.ssh")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
client.succeed("mkdir -m 700 /root/.ssh")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Install the SSH key on the server.
server.succeed("mkdir -m 700 /root/.ssh")
server.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
server.wait_for_unit("sshd")
client.wait_for_unit("network.target")
client.succeed(f"ssh -o StrictHostKeyChecking=no {server.name} 'echo hello world'")
# Install the SSH key on the server.
server.succeed("mkdir -m 700 /root/.ssh")
server.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
server.wait_for_unit("sshd")
server.wait_for_unit("multi-user.target")
server.wait_for_unit("network-online.target")
# Copy the closure of package A from the client to the server.
server.fail("nix-store --check-validity ${pkgA}")
client.succeed("nix-copy-closure --to server --gzip ${pkgA} >&2")
server.succeed("nix-store --check-validity ${pkgA}")
client.wait_for_unit("network-online.target")
client.succeed(f"ssh -o StrictHostKeyChecking=no {server.name} 'echo hello world'")
# Copy the closure of package B from the server to the client.
client.fail("nix-store --check-validity ${pkgB}")
client.succeed("nix-copy-closure --from server --gzip ${pkgB} >&2")
client.succeed("nix-store --check-validity ${pkgB}")
# Copy the closure of package A from the client to the server.
server.fail("nix-store --check-validity ${pkgA}")
client.succeed("nix-copy-closure --to server --gzip ${pkgA} >&2")
server.succeed("nix-store --check-validity ${pkgA}")
# Copy the closure of package C via the SSH substituter.
client.fail("nix-store -r ${pkgC}")
# Copy the closure of package B from the server to the client.
client.fail("nix-store --check-validity ${pkgB}")
client.succeed("nix-copy-closure --from server --gzip ${pkgB} >&2")
client.succeed("nix-store --check-validity ${pkgB}")
# Copy the derivation of package D's derivation from the client to the server.
server.fail("nix-store --check-validity ${pkgD.drvPath}")
client.succeed("nix-copy-closure --to server --gzip ${pkgD.drvPath} >&2")
server.succeed("nix-store --check-validity ${pkgD.drvPath}")
# Copy the closure of package C via the SSH substituter.
client.fail("nix-store -r ${pkgC}")
# FIXME
# client.succeed(
# "nix-store --option use-ssh-substituter true"
# " --option ssh-substituter-hosts root\@server"
# " -r ${pkgC} >&2"
# )
# client.succeed("nix-store --check-validity ${pkgC}")
'';
# Copy the derivation of package D's derivation from the client to the server.
server.fail("nix-store --check-validity ${pkgD.drvPath}")
client.succeed("nix-copy-closure --to server --gzip ${pkgD.drvPath} >&2")
server.succeed("nix-store --check-validity ${pkgD.drvPath}")
# FIXME
# client.succeed(
# "nix-store --option use-ssh-substituter true"
# " --option ssh-substituter-hosts root\@server"
# " -r ${pkgC} >&2"
# )
# client.succeed("nix-store --check-validity ${pkgC}")
'';
}

View file

@ -2,7 +2,13 @@
# Run interactively with:
# rm key key.pub; nix run .#hydraJobs.tests.nix-copy.driverInteractive
{ lib, config, nixpkgs, hostPkgs, ... }:
{
lib,
config,
nixpkgs,
hostPkgs,
...
}:
let
pkgs = config.nodes.client.nixpkgs.pkgs;
@ -12,96 +18,116 @@ let
pkgC = pkgs.hello;
pkgD = pkgs.tmux;
in {
in
{
name = "nix-copy";
enableOCR = true;
nodes =
{ client =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
virtualisation.additionalPaths = [ pkgA pkgD.drvPath ];
nix.settings.substituters = lib.mkForce [ ];
services.getty.autologinUser = "root";
programs.ssh.extraConfig = ''
Host *
ControlMaster auto
ControlPath ~/.ssh/master-%h:%r@%n:%p
ControlPersist 15m
'';
};
nodes = {
client =
{
config,
lib,
pkgs,
...
}:
{
virtualisation.writableStore = true;
virtualisation.additionalPaths = [
pkgA
pkgD.drvPath
];
nix.settings.substituters = lib.mkForce [ ];
services.getty.autologinUser = "root";
programs.ssh.extraConfig = ''
Host *
ControlMaster auto
ControlPath ~/.ssh/master-%h:%r@%n:%p
ControlPersist 15m
'';
};
server =
{ config, pkgs, ... }:
{ services.openssh.enable = true;
services.openssh.settings.PermitRootLogin = "yes";
users.users.root.password = "foobar";
virtualisation.writableStore = true;
virtualisation.additionalPaths = [ pkgB pkgC ];
};
};
server =
{ config, pkgs, ... }:
{
services.openssh.enable = true;
services.openssh.settings.PermitRootLogin = "yes";
users.users.root.hashedPasswordFile = null;
users.users.root.password = "foobar";
virtualisation.writableStore = true;
virtualisation.additionalPaths = [
pkgB
pkgC
];
};
};
testScript = { nodes }: ''
# fmt: off
import subprocess
testScript =
{ nodes }:
''
# fmt: off
import subprocess
# Create an SSH key on the client.
subprocess.run([
"${pkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
# Create an SSH key on the client.
subprocess.run([
"${pkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
start_all()
start_all()
server.wait_for_unit("sshd")
client.wait_for_unit("network.target")
client.wait_for_unit("getty@tty1.service")
# Either the prompt: ]#
# or an OCR misreading of it: 1#
client.wait_for_text("[]1]#")
server.wait_for_unit("sshd")
server.wait_for_unit("multi-user.target")
server.wait_for_unit("network-online.target")
# Copy the closure of package A from the client to the server using password authentication,
# and check that all prompts are visible
server.fail("nix-store --check-validity ${pkgA}")
client.send_chars("nix copy --to ssh://server ${pkgA} >&2; echo done\n")
client.wait_for_text("continue connecting")
client.send_chars("yes\n")
client.wait_for_text("Password:")
client.send_chars("foobar\n")
client.wait_for_text("done")
server.succeed("nix-store --check-validity ${pkgA}")
client.wait_for_unit("network-online.target")
client.wait_for_unit("getty@tty1.service")
# Either the prompt: ]#
# or an OCR misreading of it: 1#
client.wait_for_text("[]1]#")
# Check that ControlMaster is working
client.send_chars("nix copy --to ssh://server ${pkgA} >&2; echo done\n")
client.wait_for_text("done")
# Copy the closure of package A from the client to the server using password authentication,
# and check that all prompts are visible
server.fail("nix-store --check-validity ${pkgA}")
client.send_chars("nix copy --to ssh://server ${pkgA} >&2; echo -n do; echo ne\n")
client.wait_for_text("continue connecting")
client.send_chars("yes\n")
client.wait_for_text("Password:")
client.send_chars("foobar\n")
client.wait_for_text("done")
server.succeed("nix-store --check-validity ${pkgA}")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Check that ControlMaster is working
client.send_chars("nix copy --to ssh://server ${pkgA} >&2; echo done\n")
client.wait_for_text("done")
# Install the SSH key on the server.
server.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
server.succeed("systemctl restart sshd")
client.succeed(f"ssh -o StrictHostKeyChecking=no {server.name} 'echo hello world'")
client.succeed(f"ssh -O check {server.name}")
client.succeed(f"ssh -O exit {server.name}")
client.fail(f"ssh -O check {server.name}")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Check that an explicit master will work
client.succeed(f"ssh -MNfS /tmp/master {server.name}")
client.succeed(f"ssh -S /tmp/master -O check {server.name}")
client.succeed("NIX_SSHOPTS='-oControlPath=/tmp/master' nix copy --to ssh://server ${pkgA} >&2")
client.succeed(f"ssh -S /tmp/master -O exit {server.name}")
# Install the SSH key on the server.
server.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
server.succeed("systemctl restart sshd")
client.succeed(f"ssh -o StrictHostKeyChecking=no {server.name} 'echo hello world'")
client.succeed(f"ssh -O check {server.name}")
client.succeed(f"ssh -O exit {server.name}")
client.fail(f"ssh -O check {server.name}")
# Copy the closure of package B from the server to the client, using ssh-ng.
client.fail("nix-store --check-validity ${pkgB}")
# Shouldn't download untrusted paths by default
client.fail("nix copy --from ssh-ng://server ${pkgB} >&2")
client.succeed("nix copy --no-check-sigs --from ssh-ng://server ${pkgB} >&2")
client.succeed("nix-store --check-validity ${pkgB}")
# Check that an explicit master will work
client.succeed(f"ssh -MNfS /tmp/master {server.name}")
client.succeed(f"ssh -S /tmp/master -O check {server.name}")
client.succeed("NIX_SSHOPTS='-oControlPath=/tmp/master' nix copy --to ssh://server ${pkgA} >&2")
client.succeed(f"ssh -S /tmp/master -O exit {server.name}")
# Copy the derivation of package D's derivation from the client to the server.
server.fail("nix-store --check-validity ${pkgD.drvPath}")
client.succeed("nix copy --derivation --to ssh://server ${pkgD.drvPath} >&2")
server.succeed("nix-store --check-validity ${pkgD.drvPath}")
'';
# Copy the closure of package B from the server to the client, using ssh-ng.
client.fail("nix-store --check-validity ${pkgB}")
# Shouldn't download untrusted paths by default
client.fail("nix copy --from ssh-ng://server ${pkgB} >&2")
client.succeed("nix copy --no-check-sigs --from ssh-ng://server ${pkgB} >&2")
client.succeed("nix-store --check-validity ${pkgB}")
# Copy the derivation of package D's derivation from the client to the server.
server.fail("nix-store --check-validity ${pkgD.drvPath}")
client.succeed("nix copy --derivation --to ssh://server ${pkgD.drvPath} >&2")
server.succeed("nix-store --check-validity ${pkgD.drvPath}")
'';
}

View file

@ -0,0 +1,47 @@
#!/usr/bin/env bash
# docker.nix test script. Runs inside a built docker.nix container.
set -eEuo pipefail
export NIX_CONFIG='substituters = http://cache:5000?trusted=1'
cd /tmp
# Test getting a fetched derivation
test "$("$(nix-build -E '(import <nixpkgs> {}).hello')"/bin/hello)" = "Hello, world!"
# Test building a simple derivation
# shellcheck disable=SC2016
nix-build -E '
let
pkgs = import <nixpkgs> {};
in
builtins.derivation {
name = "test";
system = builtins.currentSystem;
builder = "${pkgs.bash}/bin/bash";
args = ["-c" "echo OK > $out"];
}'
test "$(cat result)" = OK
# Ensure #!/bin/sh shebang works
echo '#!/bin/sh' > ./shebang-test
echo 'echo OK' >> ./shebang-test
chmod +x ./shebang-test
test "$(./shebang-test)" = OK
# Ensure #!/usr/bin/env shebang works
echo '#!/usr/bin/env bash' > ./shebang-test
echo 'echo OK' >> ./shebang-test
chmod +x ./shebang-test
test "$(./shebang-test)" = OK
# Test nix-shell
{
echo '#!/usr/bin/env nix-shell'
echo '#! nix-shell -i bash'
echo '#! nix-shell -p hello'
echo 'hello'
} > ./nix-shell-test
chmod +x ./nix-shell-test
test "$(./nix-shell-test)" = "Hello, world!"

View file

@ -0,0 +1,78 @@
# Test the container built by ../../docker.nix.
{
lib,
config,
nixpkgs,
hostPkgs,
...
}:
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
nixImage = import ../../docker.nix {
inherit (config.nodes.machine.nixpkgs) pkgs;
};
nixUserImage = import ../../docker.nix {
inherit (config.nodes.machine.nixpkgs) pkgs;
name = "nix-user";
uid = 1000;
gid = 1000;
uname = "user";
gname = "user";
};
containerTestScript = ./nix-docker-test.sh;
in
{
name = "nix-docker";
nodes = {
machine =
{
config,
lib,
pkgs,
...
}:
{
virtualisation.diskSize = 4096;
};
cache =
{
config,
lib,
pkgs,
...
}:
{
virtualisation.additionalPaths = [
pkgs.stdenv
pkgs.hello
];
services.harmonia.enable = true;
networking.firewall.allowedTCPPorts = [ 5000 ];
};
};
testScript =
{ nodes }:
''
cache.wait_for_unit("harmonia.service")
cache.wait_for_unit("network-online.target")
machine.succeed("mkdir -p /etc/containers")
machine.succeed("""echo '{"default":[{"type":"insecureAcceptAnything"}]}' > /etc/containers/policy.json""")
machine.succeed("${pkgs.podman}/bin/podman load -i ${nixImage}")
machine.succeed("${pkgs.podman}/bin/podman run --rm nix nix --version")
machine.succeed("${pkgs.podman}/bin/podman run --rm -i nix < ${containerTestScript}")
machine.succeed("${pkgs.podman}/bin/podman load -i ${nixUserImage}")
machine.succeed("${pkgs.podman}/bin/podman run --rm nix-user nix --version")
machine.succeed("${pkgs.podman}/bin/podman run --rm -i nix-user < ${containerTestScript}")
machine.succeed("[[ $(${pkgs.podman}/bin/podman run --rm nix-user stat -c %u /nix/store) = 1000 ]]")
'';
}

View file

@ -1,4 +1,9 @@
{ lib, config, nixpkgs, ... }:
{
lib,
config,
nixpkgs,
...
}:
let
@ -44,79 +49,119 @@ in
name = "nss-preload";
nodes = {
http_dns = { lib, pkgs, config, ... }: {
networking.firewall.enable = false;
networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
{ address = "fd21::1"; prefixLength = 64; }
];
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
{ address = "192.168.0.1"; prefixLength = 24; }
];
http_dns =
{
lib,
pkgs,
config,
...
}:
{
networking.firewall.enable = false;
networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
{
address = "fd21::1";
prefixLength = 64;
}
];
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
{
address = "192.168.0.1";
prefixLength = 24;
}
];
services.unbound = {
enable = true;
enableRootTrustAnchor = false;
settings = {
server = {
interface = [ "192.168.0.1" "fd21::1" "::1" "127.0.0.1" ];
access-control = [ "192.168.0.0/24 allow" "fd21::/64 allow" "::1 allow" "127.0.0.0/8 allow" ];
local-data = [
''"example.com. IN A 192.168.0.1"''
''"example.com. IN AAAA fd21::1"''
''"tarballs.nixos.org. IN A 192.168.0.1"''
''"tarballs.nixos.org. IN AAAA fd21::1"''
];
services.unbound = {
enable = true;
enableRootTrustAnchor = false;
settings = {
server = {
interface = [
"192.168.0.1"
"fd21::1"
"::1"
"127.0.0.1"
];
access-control = [
"192.168.0.0/24 allow"
"fd21::/64 allow"
"::1 allow"
"127.0.0.0/8 allow"
];
local-data = [
''"example.com. IN A 192.168.0.1"''
''"example.com. IN AAAA fd21::1"''
''"tarballs.nixos.org. IN A 192.168.0.1"''
''"tarballs.nixos.org. IN AAAA fd21::1"''
];
};
};
};
services.nginx = {
enable = true;
virtualHosts."example.com" = {
root = pkgs.runCommand "testdir" { } ''
mkdir "$out"
echo hello world > "$out/index.html"
'';
};
};
};
services.nginx = {
enable = true;
virtualHosts."example.com" = {
root = pkgs.runCommand "testdir" {} ''
mkdir "$out"
echo hello world > "$out/index.html"
'';
};
};
};
# client consumes a remote resolver
client = { lib, nodes, pkgs, ... }: {
networking.useDHCP = false;
networking.nameservers = [
(lib.head nodes.http_dns.networking.interfaces.eth1.ipv6.addresses).address
(lib.head nodes.http_dns.networking.interfaces.eth1.ipv4.addresses).address
];
networking.interfaces.eth1.ipv6.addresses = [
{ address = "fd21::10"; prefixLength = 64; }
];
networking.interfaces.eth1.ipv4.addresses = [
{ address = "192.168.0.10"; prefixLength = 24; }
];
client =
{
lib,
nodes,
pkgs,
...
}:
{
networking.useDHCP = false;
networking.nameservers = [
(lib.head nodes.http_dns.networking.interfaces.eth1.ipv6.addresses).address
(lib.head nodes.http_dns.networking.interfaces.eth1.ipv4.addresses).address
];
networking.interfaces.eth1.ipv6.addresses = [
{
address = "fd21::10";
prefixLength = 64;
}
];
networking.interfaces.eth1.ipv4.addresses = [
{
address = "192.168.0.10";
prefixLength = 24;
}
];
nix.settings.extra-sandbox-paths = lib.mkForce [];
nix.settings.substituters = lib.mkForce [];
nix.settings.sandbox = lib.mkForce true;
};
nix.settings.extra-sandbox-paths = lib.mkForce [ ];
nix.settings.substituters = lib.mkForce [ ];
nix.settings.sandbox = lib.mkForce true;
};
};
testScript = { nodes, ... }: ''
http_dns.wait_for_unit("nginx")
http_dns.wait_for_open_port(80)
http_dns.wait_for_unit("unbound")
http_dns.wait_for_open_port(53)
testScript =
{ nodes, ... }:
''
http_dns.wait_for_unit("network-online.target")
http_dns.wait_for_unit("nginx")
http_dns.wait_for_open_port(80)
http_dns.wait_for_unit("unbound")
http_dns.wait_for_open_port(53)
client.start()
client.wait_for_unit('multi-user.target')
client.start()
client.wait_for_unit('multi-user.target')
client.wait_for_unit('network-online.target')
with subtest("can fetch data from a remote server outside sandbox"):
client.succeed("nix --version >&2")
client.succeed("curl -vvv http://example.com/index.html >&2")
with subtest("can fetch data from a remote server outside sandbox"):
client.succeed("nix --version >&2")
client.succeed("curl -vvv http://example.com/index.html >&2")
with subtest("nix-build can lookup dns and fetch data"):
client.succeed("""
nix-build ${nix-fetch} >&2
""")
'';
with subtest("nix-build can lookup dns and fetch data"):
client.succeed("""
nix-build ${nix-fetch} >&2
""")
'';
}

View file

@ -1,47 +0,0 @@
test@{ lib, extendModules, ... }:
let
inherit (lib) mkOption types;
in
{
options = {
quickBuild = mkOption {
description = ''
Whether to perform a "quick" build of the Nix package to test.
When iterating on the functional tests, it's recommended to "set" this
to `true`, so that changes to the functional tests don't require any
recompilation of the package.
You can do so by building the `.quickBuild` attribute on the check package,
e.g:
```console
nix build .#hydraJobs.functional_user.quickBuild
```
We don't enable this by default to avoid the mostly unnecessary work of
performing an additional build of the package in cases where we build
the package normally anyway, such as in our pre-merge CI.
'';
type = types.bool;
default = false;
};
};
config = {
passthru.quickBuild =
let withQuickBuild = extendModules { modules = [{ quickBuild = true; }]; };
in withQuickBuild.config.test;
defaults = { pkgs, ... }: {
config = lib.mkIf test.config.quickBuild {
nix.package = pkgs.nix_noTests;
system.forbiddenDependenciesRegexes = [
# This would indicate that the quickBuild feature is broken.
# It could happen if NixOS has a dependency on pkgs.nix instead of
# config.nix.package somewhere.
(builtins.unsafeDiscardStringContext pkgs.nix.outPath)
];
};
};
};
}

View file

@ -1,11 +1,17 @@
test@{ config, lib, hostPkgs, ... }:
test@{
config,
lib,
hostPkgs,
...
}:
let
pkgs = config.nodes.client.nixpkgs.pkgs;
# Trivial Nix expression to build remotely.
expr = config: nr: pkgs.writeText "expr.nix"
''
expr =
config: nr:
pkgs.writeText "expr.nix" ''
let utils = builtins.storePath ${config.system.build.extraUtils}; in
derivation {
name = "hello-${toString nr}";
@ -41,84 +47,94 @@ in
config = {
name = lib.mkDefault "remote-builds-ssh-ng";
nodes =
{
builder =
{ config, pkgs, ... }:
{
imports = [ test.config.builders.config ];
services.openssh.enable = true;
virtualisation.writableStore = true;
nix.settings.sandbox = true;
nix.settings.substituters = lib.mkForce [ ];
};
nodes = {
builder =
{ config, pkgs, ... }:
{
imports = [ test.config.builders.config ];
services.openssh.enable = true;
virtualisation.writableStore = true;
nix.settings.sandbox = true;
nix.settings.substituters = lib.mkForce [ ];
};
client =
{ config, lib, pkgs, ... }:
{
nix.settings.max-jobs = 0; # force remote building
nix.distributedBuilds = true;
nix.buildMachines =
[{
hostName = "builder";
sshUser = "root";
sshKey = "/root/.ssh/id_ed25519";
system = "i686-linux";
maxJobs = 1;
protocol = "ssh-ng";
}];
virtualisation.writableStore = true;
virtualisation.additionalPaths = [ config.system.build.extraUtils ];
nix.settings.substituters = lib.mkForce [ ];
programs.ssh.extraConfig = "ConnectTimeout 30";
};
};
client =
{
config,
lib,
pkgs,
...
}:
{
nix.settings.max-jobs = 0; # force remote building
nix.distributedBuilds = true;
nix.buildMachines = [
{
hostName = "builder";
sshUser = "root";
sshKey = "/root/.ssh/id_ed25519";
system = "i686-linux";
maxJobs = 1;
protocol = "ssh-ng";
}
];
virtualisation.writableStore = true;
virtualisation.additionalPaths = [ config.system.build.extraUtils ];
nix.settings.substituters = lib.mkForce [ ];
programs.ssh.extraConfig = "ConnectTimeout 30";
};
};
testScript = { nodes }: ''
# fmt: off
import subprocess
testScript =
{ nodes }:
''
# fmt: off
import subprocess
start_all()
start_all()
# Create an SSH key on the client.
subprocess.run([
"${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
client.succeed("mkdir -p -m 700 /root/.ssh")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Create an SSH key on the client.
subprocess.run([
"${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
client.succeed("mkdir -p -m 700 /root/.ssh")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Install the SSH key on the builder.
client.wait_for_unit("network.target")
builder.succeed("mkdir -p -m 700 /root/.ssh")
builder.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
builder.wait_for_unit("sshd")
client.succeed(f"ssh -o StrictHostKeyChecking=no {builder.name} 'echo hello world'")
# Install the SSH key on the builder.
client.wait_for_unit("network-online.target")
builder.succeed("mkdir -p -m 700 /root/.ssh")
builder.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
builder.wait_for_unit("sshd")
builder.wait_for_unit("multi-user.target")
builder.wait_for_unit("network-online.target")
# Perform a build
out = client.succeed("nix-build ${expr nodes.client 1} 2> build-output")
client.succeed(f"ssh -o StrictHostKeyChecking=no {builder.name} 'echo hello world'")
# Verify that the build was done on the builder
builder.succeed(f"test -e {out.strip()}")
# Perform a build
out = client.succeed("nix-build ${expr nodes.client 1} 2> build-output")
# Print the build log, prefix the log lines to avoid nix intercepting lines starting with @nix
buildOutput = client.succeed("sed -e 's/^/build-output:/' build-output")
print(buildOutput)
# Verify that the build was done on the builder
builder.succeed(f"test -e {out.strip()}")
# Make sure that we get the expected build output
client.succeed("grep -qF Hello build-output")
# Print the build log, prefix the log lines to avoid nix intercepting lines starting with @nix
buildOutput = client.succeed("sed -e 's/^/build-output:/' build-output")
print(buildOutput)
# We don't want phase reporting in the build output
client.fail("grep -qF '@nix' build-output")
# Make sure that we get the expected build output
client.succeed("grep -qF Hello build-output")
# Get the log file
client.succeed(f"nix-store --read-log {out.strip()} > log-output")
# Prefix the log lines to avoid nix intercepting lines starting with @nix
logOutput = client.succeed("sed -e 's/^/log-file:/' log-output")
print(logOutput)
# We don't want phase reporting in the build output
client.fail("grep -qF '@nix' build-output")
# Check that we get phase reporting in the log file
client.succeed("grep -q '@nix {\"action\":\"setPhase\",\"phase\":\"buildPhase\"}' log-output")
'';
# Get the log file
client.succeed(f"nix-store --read-log {out.strip()} > log-output")
# Prefix the log lines to avoid nix intercepting lines starting with @nix
logOutput = client.succeed("sed -e 's/^/log-file:/' log-output")
print(logOutput)
# Check that we get phase reporting in the log file
client.succeed("grep -q '@nix {\"action\":\"setPhase\",\"phase\":\"buildPhase\"}' log-output")
'';
};
}

View file

@ -1,6 +1,11 @@
# Test Nix's remote build feature.
test@{ config, lib, hostPkgs, ... }:
test@{
config,
lib,
hostPkgs,
...
}:
let
pkgs = config.nodes.client.nixpkgs.pkgs;
@ -21,8 +26,9 @@ let
};
# Trivial Nix expression to build remotely.
expr = config: nr: pkgs.writeText "expr.nix"
''
expr =
config: nr:
pkgs.writeText "expr.nix" ''
let utils = builtins.storePath ${config.system.build.extraUtils}; in
derivation {
name = "hello-${toString nr}";
@ -34,6 +40,8 @@ let
}
'';
supportsBadShell = lib.versionAtLeast config.nodes.client.nix.package.version "2.25pre";
in
{
@ -50,89 +58,112 @@ in
config = {
name = lib.mkDefault "remote-builds";
nodes =
{
builder1 = builder;
builder2 = builder;
nodes = {
builder1 = builder;
builder2 = builder;
client =
{ config, lib, pkgs, ... }:
{
nix.settings.max-jobs = 0; # force remote building
nix.distributedBuilds = true;
nix.buildMachines =
[
{
hostName = "builder1";
sshUser = "root";
sshKey = "/root/.ssh/id_ed25519";
system = "i686-linux";
maxJobs = 1;
}
{
hostName = "builder2";
sshUser = "root";
sshKey = "/root/.ssh/id_ed25519";
system = "i686-linux";
maxJobs = 1;
}
];
virtualisation.writableStore = true;
virtualisation.additionalPaths = [ config.system.build.extraUtils ];
nix.settings.substituters = lib.mkForce [ ];
programs.ssh.extraConfig = "ConnectTimeout 30";
};
};
client =
{
config,
lib,
pkgs,
...
}:
{
nix.settings.max-jobs = 0; # force remote building
nix.distributedBuilds = true;
nix.buildMachines = [
{
hostName = "builder1";
sshUser = "root";
sshKey = "/root/.ssh/id_ed25519";
system = "i686-linux";
maxJobs = 1;
}
{
hostName = "builder2";
sshUser = "root";
sshKey = "/root/.ssh/id_ed25519";
system = "i686-linux";
maxJobs = 1;
}
];
virtualisation.writableStore = true;
virtualisation.additionalPaths = [ config.system.build.extraUtils ];
nix.settings.substituters = lib.mkForce [ ];
programs.ssh.extraConfig = "ConnectTimeout 30";
environment.systemPackages = [
# `bad-shell` is used to make sure Nix works in an environment with a misbehaving shell.
#
# More realistically, a bad shell would still run the command ("echo started")
# but considering that our solution is to avoid this shell (set via $SHELL), we
# don't need to bother with a more functional mock shell.
(pkgs.writeScriptBin "bad-shell" ''
#!${pkgs.runtimeShell}
echo "Hello, I am a broken shell"
'')
];
};
};
testScript = { nodes }: ''
# fmt: off
import subprocess
testScript =
{ nodes }:
''
# fmt: off
import subprocess
start_all()
start_all()
# Create an SSH key on the client.
subprocess.run([
"${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
client.succeed("mkdir -p -m 700 /root/.ssh")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Create an SSH key on the client.
subprocess.run([
"${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", ""
], capture_output=True, check=True)
client.succeed("mkdir -p -m 700 /root/.ssh")
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
# Install the SSH key on the builders.
client.wait_for_unit("network.target")
for builder in [builder1, builder2]:
builder.succeed("mkdir -p -m 700 /root/.ssh")
builder.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
builder.wait_for_unit("sshd")
# Make sure the builder can handle our login correctly
builder.wait_for_unit("multi-user.target")
# Make sure there's no funny business on the client either
# (should not be necessary, but we have reason to be careful)
client.wait_for_unit("multi-user.target")
client.succeed(f"""
ssh -o StrictHostKeyChecking=no {builder.name} \
'echo hello world on $(hostname)' >&2
""")
# Install the SSH key on the builders.
client.wait_for_unit("network-online.target")
for builder in [builder1, builder2]:
builder.succeed("mkdir -p -m 700 /root/.ssh")
builder.copy_from_host("key.pub", "/root/.ssh/authorized_keys")
builder.wait_for_unit("sshd")
builder.wait_for_unit("network-online.target")
# Make sure the builder can handle our login correctly
builder.wait_for_unit("multi-user.target")
# Make sure there's no funny business on the client either
# (should not be necessary, but we have reason to be careful)
client.wait_for_unit("multi-user.target")
client.succeed(f"""
ssh -o StrictHostKeyChecking=no {builder.name} \
'echo hello world on $(hostname)' >&2
""")
# Perform a build and check that it was performed on the builder.
out = client.succeed(
"nix-build ${expr nodes.client 1} 2> build-output",
"grep -q Hello build-output"
)
builder1.succeed(f"test -e {out}")
${lib.optionalString supportsBadShell ''
# Check that SSH uses SHELL for LocalCommand, as expected, and check that
# our test setup here is working. The next test will use this bad SHELL.
client.succeed(f"SHELL=$(which bad-shell) ssh -oLocalCommand='true' -oPermitLocalCommand=yes {builder1.name} 'echo hello world' | grep -F 'Hello, I am a broken shell'")
''}
# And a parallel build.
paths = client.succeed(r'nix-store -r $(nix-instantiate ${expr nodes.client 2})\!out $(nix-instantiate ${expr nodes.client 3})\!out')
out1, out2 = paths.split()
builder1.succeed(f"test -e {out1} -o -e {out2}")
builder2.succeed(f"test -e {out1} -o -e {out2}")
# Perform a build and check that it was performed on the builder.
out = client.succeed(
"${lib.optionalString supportsBadShell "SHELL=$(which bad-shell)"} nix-build ${expr nodes.client 1} 2> build-output",
"grep -q Hello build-output"
)
builder1.succeed(f"test -e {out}")
# And a failing build.
client.fail("nix-build ${expr nodes.client 5}")
# And a parallel build.
paths = client.succeed(r'nix-store -r $(nix-instantiate ${expr nodes.client 2})\!out $(nix-instantiate ${expr nodes.client 3})\!out')
out1, out2 = paths.split()
builder1.succeed(f"test -e {out1} -o -e {out2}")
builder2.succeed(f"test -e {out1} -o -e {out2}")
# Test whether the build hook automatically skips unavailable builders.
builder1.block()
client.succeed("nix-build ${expr nodes.client 4}")
'';
# And a failing build.
client.fail("nix-build ${expr nodes.client 5}")
# Test whether the build hook automatically skips unavailable builders.
builder1.block()
client.succeed("nix-build ${expr nodes.client 4}")
'';
};
}

View file

@ -1,4 +1,9 @@
{ lib, config, nixpkgs, ... }:
{
lib,
config,
nixpkgs,
...
}:
let
pkgs = config.nodes.client.nixpkgs.pkgs;
@ -10,57 +15,81 @@ let
env = "AWS_ACCESS_KEY_ID=${accessKey} AWS_SECRET_ACCESS_KEY=${secretKey}";
storeUrl = "s3://my-cache?endpoint=http://server:9000&region=eu-west-1";
objectThatDoesNotExist = "s3://my-cache/foo-that-does-not-exist?endpoint=http://server:9000&region=eu-west-1";
in {
name = "nix-copy-closure";
in
{
name = "s3-binary-cache-store";
nodes =
{ server =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
virtualisation.additionalPaths = [ pkgA ];
environment.systemPackages = [ pkgs.minio-client ];
nix.extraOptions = "experimental-features = nix-command";
services.minio = {
enable = true;
region = "eu-west-1";
rootCredentialsFile = pkgs.writeText "minio-credentials-full" ''
MINIO_ROOT_USER=${accessKey}
MINIO_ROOT_PASSWORD=${secretKey}
'';
};
networking.firewall.allowedTCPPorts = [ 9000 ];
nodes = {
server =
{
config,
lib,
pkgs,
...
}:
{
virtualisation.writableStore = true;
virtualisation.additionalPaths = [ pkgA ];
environment.systemPackages = [ pkgs.minio-client ];
nix.extraOptions = ''
substituters =
'';
services.minio = {
enable = true;
region = "eu-west-1";
rootCredentialsFile = pkgs.writeText "minio-credentials-full" ''
MINIO_ROOT_USER=${accessKey}
MINIO_ROOT_PASSWORD=${secretKey}
'';
};
networking.firewall.allowedTCPPorts = [ 9000 ];
};
client =
{ config, pkgs, ... }:
{ virtualisation.writableStore = true;
nix.extraOptions = "experimental-features = nix-command";
};
};
client =
{ config, pkgs, ... }:
{
virtualisation.writableStore = true;
nix.extraOptions = ''
substituters =
'';
};
};
testScript = { nodes }: ''
# fmt: off
start_all()
testScript =
{ nodes }:
''
# fmt: off
start_all()
# Create a binary cache.
server.wait_for_unit("minio")
# Create a binary cache.
server.wait_for_unit("minio")
server.wait_for_unit("network-online.target")
server.succeed("mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4")
server.succeed("mc mb minio/my-cache")
server.succeed("mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4")
server.succeed("mc mb minio/my-cache")
server.succeed("${env} nix copy --to '${storeUrl}' ${pkgA}")
server.succeed("${env} nix copy --to '${storeUrl}' ${pkgA}")
# Test fetchurl on s3:// URLs while we're at it.
client.succeed("${env} nix eval --impure --expr 'builtins.fetchurl { name = \"foo\"; url = \"s3://my-cache/nix-cache-info?endpoint=http://server:9000&region=eu-west-1\"; }'")
client.wait_for_unit("network-online.target")
# Copy a package from the binary cache.
client.fail("nix path-info ${pkgA}")
# Test fetchurl on s3:// URLs while we're at it.
client.succeed("${env} nix eval --impure --expr 'builtins.fetchurl { name = \"foo\"; url = \"s3://my-cache/nix-cache-info?endpoint=http://server:9000&region=eu-west-1\"; }'")
client.succeed("${env} nix store info --store '${storeUrl}' >&2")
# Test that the format string in the error message is properly setup and won't display `%s` instead of the failed URI
msg = client.fail("${env} nix eval --impure --expr 'builtins.fetchurl { name = \"foo\"; url = \"${objectThatDoesNotExist}\"; }' 2>&1")
if "S3 object '${objectThatDoesNotExist}' does not exist" not in msg:
print(msg) # So that you can see the message that was improperly formatted
raise Exception("Error message formatting didn't work")
client.succeed("${env} nix copy --no-check-sigs --from '${storeUrl}' ${pkgA}")
# Copy a package from the binary cache.
client.fail("nix path-info ${pkgA}")
client.succeed("nix path-info ${pkgA}")
'';
client.succeed("${env} nix store info --store '${storeUrl}' >&2")
client.succeed("${env} nix copy --no-check-sigs --from '${storeUrl}' ${pkgA}")
client.succeed("nix path-info ${pkgA}")
'';
}

View file

@ -1,6 +1,11 @@
# Verify that Linux builds cannot create setuid or setgid binaries.
{ lib, config, nixpkgs, ... }:
{
lib,
config,
nixpkgs,
...
}:
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
@ -10,116 +15,127 @@ in
name = "setuid";
nodes.machine =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
{
config,
lib,
pkgs,
...
}:
{
virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ];
virtualisation.additionalPaths = [ pkgs.stdenvNoCC pkgs.pkgsi686Linux.stdenvNoCC ];
virtualisation.additionalPaths = [
pkgs.stdenvNoCC
pkgs.pkgsi686Linux.stdenvNoCC
];
};
testScript = { nodes }: ''
# fmt: off
start_all()
testScript =
{ nodes }:
''
# fmt: off
start_all()
# Copying to /tmp should succeed.
machine.succeed(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
")'
""".strip())
# Copying to /tmp should succeed.
machine.succeed(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
")'
""".strip())
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed("rm /tmp/id")
machine.succeed("rm /tmp/id")
# Creating a setuid binary should fail.
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
chmod 4755 /tmp/id
")'
""".strip())
# Creating a setuid binary should fail.
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
chmod 4755 /tmp/id
")'
""".strip())
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed("rm /tmp/id")
machine.succeed("rm /tmp/id")
# Creating a setgid binary should fail.
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
chmod 2755 /tmp/id
")'
""".strip())
# Creating a setgid binary should fail.
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" {} "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
chmod 2755 /tmp/id
")'
""".strip())
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed("rm /tmp/id")
machine.succeed("rm /tmp/id")
# The checks should also work on 32-bit binaries.
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> { system = "i686-linux"; }; runCommand "foo" {} "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
chmod 2755 /tmp/id
")'
""".strip())
# The checks should also work on 32-bit binaries.
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> { system = "i686-linux"; }; runCommand "foo" {} "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
chmod 2755 /tmp/id
")'
""".strip())
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed("rm /tmp/id")
machine.succeed("rm /tmp/id")
# The tests above use fchmodat(). Test chmod() as well.
machine.succeed(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" { buildInputs = [ perl ]; } "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
perl -e \"chmod 0666, qw(/tmp/id) or die\"
")'
""".strip())
# The tests above use fchmodat(). Test chmod() as well.
machine.succeed(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" { buildInputs = [ perl ]; } "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
perl -e \"chmod 0666, qw(/tmp/id) or die\"
")'
""".strip())
machine.succeed('[[ $(stat -c %a /tmp/id) = 666 ]]')
machine.succeed('[[ $(stat -c %a /tmp/id) = 666 ]]')
machine.succeed("rm /tmp/id")
machine.succeed("rm /tmp/id")
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" { buildInputs = [ perl ]; } "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
perl -e \"chmod 04755, qw(/tmp/id) or die\"
")'
""".strip())
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" { buildInputs = [ perl ]; } "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
perl -e \"chmod 04755, qw(/tmp/id) or die\"
")'
""".strip())
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed("rm /tmp/id")
machine.succeed("rm /tmp/id")
# And test fchmod().
machine.succeed(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" { buildInputs = [ perl ]; } "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
perl -e \"my \\\$x; open \\\$x, qw(/tmp/id); chmod 01750, \\\$x or die\"
")'
""".strip())
# And test fchmod().
machine.succeed(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" { buildInputs = [ perl ]; } "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
perl -e \"my \\\$x; open \\\$x, qw(/tmp/id); chmod 01750, \\\$x or die\"
")'
""".strip())
machine.succeed('[[ $(stat -c %a /tmp/id) = 1750 ]]')
machine.succeed('[[ $(stat -c %a /tmp/id) = 1750 ]]')
machine.succeed("rm /tmp/id")
machine.succeed("rm /tmp/id")
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" { buildInputs = [ perl ]; } "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
perl -e \"my \\\$x; open \\\$x, qw(/tmp/id); chmod 04777, \\\$x or die\"
")'
""".strip())
machine.fail(r"""
nix-build --no-sandbox -E '(with import <nixpkgs> {}; runCommand "foo" { buildInputs = [ perl ]; } "
mkdir -p $out
cp ${pkgs.coreutils}/bin/id /tmp/id
perl -e \"my \\\$x; open \\\$x, qw(/tmp/id); chmod 04777, \\\$x or die\"
")'
""".strip())
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
machine.succeed("rm /tmp/id")
'';
machine.succeed("rm /tmp/id")
'';
}

View file

@ -1,22 +1,27 @@
{ lib, config, hostPkgs, nixpkgs, ... }:
{
lib,
config,
hostPkgs,
nixpkgs,
...
}:
let
pkgs = config.nodes.sourcehut.nixpkgs.pkgs;
# Generate a fake root CA and a fake git.sr.ht certificate.
cert = pkgs.runCommand "cert" { buildInputs = [ pkgs.openssl ]; }
''
mkdir -p $out
cert = pkgs.runCommand "cert" { buildInputs = [ pkgs.openssl ]; } ''
mkdir -p $out
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 36500 -key ca.key \
-subj "/C=NL/ST=Denial/L=Springfield/O=Dis/CN=Root CA" -out $out/ca.crt
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 36500 -key ca.key \
-subj "/C=NL/ST=Denial/L=Springfield/O=Dis/CN=Root CA" -out $out/ca.crt
openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \
-subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=git.sr.ht" -out server.csr
openssl x509 -req -extfile <(printf "subjectAltName=DNS:git.sr.ht") \
-days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt
'';
openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \
-subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=git.sr.ht" -out server.csr
openssl x509 -req -extfile <(printf "subjectAltName=DNS:git.sr.ht") \
-days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt
'';
registry = pkgs.writeTextFile {
name = "registry";
@ -41,79 +46,91 @@ let
destination = "/flake-registry.json";
};
nixpkgs-repo = pkgs.runCommand "nixpkgs-flake" { }
''
dir=NixOS-nixpkgs-${nixpkgs.shortRev}
cp -prd ${nixpkgs} $dir
nixpkgs-repo = pkgs.runCommand "nixpkgs-flake" { } ''
dir=NixOS-nixpkgs-${nixpkgs.shortRev}
cp -prd ${nixpkgs} $dir
# Set the correct timestamp in the tarball.
find $dir -print0 | xargs -0 touch -h -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${builtins.substring 12 2 nixpkgs.lastModifiedDate} --
# Set the correct timestamp in the tarball.
find $dir -print0 | xargs -0 touch -h -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${
builtins.substring 12 2 nixpkgs.lastModifiedDate
} --
mkdir -p $out/archive
tar cfz $out/archive/${nixpkgs.rev}.tar.gz $dir --hard-dereference
mkdir -p $out/archive
tar cfz $out/archive/${nixpkgs.rev}.tar.gz $dir --hard-dereference
echo 'ref: refs/heads/master' > $out/HEAD
echo 'ref: refs/heads/master' > $out/HEAD
mkdir -p $out/info
echo -e '${nixpkgs.rev}\trefs/heads/master\n${nixpkgs.rev}\trefs/tags/foo-bar' > $out/info/refs
'';
mkdir -p $out/info
echo -e '${nixpkgs.rev}\trefs/heads/master\n${nixpkgs.rev}\trefs/tags/foo-bar' > $out/info/refs
'';
in
{
name = "sourcehut-flakes";
{
name = "sourcehut-flakes";
nodes =
nodes = {
# Impersonate git.sr.ht
sourcehut =
{ config, pkgs, ... }:
{
# Impersonate git.sr.ht
sourcehut =
{ config, pkgs, ... }:
{
networking.firewall.allowedTCPPorts = [ 80 443 ];
networking.firewall.allowedTCPPorts = [
80
443
];
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
services.httpd.extraConfig = ''
ErrorLog syslog:local6
'';
services.httpd.virtualHosts."git.sr.ht" =
{
forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs =
[
{
urlPath = "/~NixOS/nixpkgs";
dir = nixpkgs-repo;
}
{
urlPath = "/~NixOS/flake-registry/blob/master";
dir = registry;
}
];
};
};
client =
{ config, lib, pkgs, nodes, ... }:
{
virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
nix.extraOptions = ''
flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json
'';
environment.systemPackages = [ pkgs.jq ];
networking.hosts.${(builtins.head nodes.sourcehut.networking.interfaces.eth1.ipv4.addresses).address} =
[ "git.sr.ht" ];
security.pki.certificateFiles = [ "${cert}/ca.crt" ];
};
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
services.httpd.extraConfig = ''
ErrorLog syslog:local6
'';
services.httpd.virtualHosts."git.sr.ht" = {
forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs = [
{
urlPath = "/~NixOS/nixpkgs";
dir = nixpkgs-repo;
}
{
urlPath = "/~NixOS/flake-registry/blob/master";
dir = registry;
}
];
};
};
testScript = { nodes }: ''
client =
{
config,
lib,
pkgs,
nodes,
...
}:
{
virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [
pkgs.hello
pkgs.fuse
];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
nix.extraOptions = ''
flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json
'';
environment.systemPackages = [ pkgs.jq ];
networking.hosts.${(builtins.head nodes.sourcehut.networking.interfaces.eth1.ipv4.addresses).address} =
[ "git.sr.ht" ];
security.pki.certificateFiles = [ "${cert}/ca.crt" ];
};
};
testScript =
{ nodes }:
''
# fmt: off
import json
import time
@ -121,6 +138,8 @@ in
start_all()
sourcehut.wait_for_unit("httpd.service")
sourcehut.wait_for_unit("network-online.target")
client.wait_for_unit("network-online.target")
client.succeed("curl -v https://git.sr.ht/ >&2")
client.succeed("nix registry list | grep nixpkgs")

View file

@ -1,93 +1,105 @@
{ lib, config, nixpkgs, ... }:
{
lib,
config,
nixpkgs,
...
}:
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
root = pkgs.runCommand "nixpkgs-flake" {}
''
mkdir -p $out/{stable,tags}
root = pkgs.runCommand "nixpkgs-flake" { } ''
mkdir -p $out/{stable,tags}
set -x
dir=nixpkgs-${nixpkgs.shortRev}
cp -prd ${nixpkgs} $dir
# Set the correct timestamp in the tarball.
find $dir -print0 | xargs -0 touch -h -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${builtins.substring 12 2 nixpkgs.lastModifiedDate} --
tar cfz $out/stable/${nixpkgs.rev}.tar.gz $dir --hard-dereference
set -x
dir=nixpkgs-${nixpkgs.shortRev}
cp -prd ${nixpkgs} $dir
# Set the correct timestamp in the tarball.
find $dir -print0 | xargs -0 touch -h -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${
builtins.substring 12 2 nixpkgs.lastModifiedDate
} --
tar cfz $out/stable/${nixpkgs.rev}.tar.gz $dir --hard-dereference
# Set the "Link" header on the redirect but not the final response to
# simulate an S3-like serving environment where the final host cannot set
# arbitrary headers.
cat >$out/tags/.htaccess <<EOF
Redirect "/tags/latest.tar.gz" "/stable/${nixpkgs.rev}.tar.gz"
Header always set Link "<http://localhost/stable/${nixpkgs.rev}.tar.gz?rev=${nixpkgs.rev}&revCount=1234>; rel=\"immutable\""
EOF
'';
# Set the "Link" header on the redirect but not the final response to
# simulate an S3-like serving environment where the final host cannot set
# arbitrary headers.
cat >$out/tags/.htaccess <<EOF
Redirect "/tags/latest.tar.gz" "/stable/${nixpkgs.rev}.tar.gz"
Header always set Link "<http://localhost/stable/${nixpkgs.rev}.tar.gz?rev=${nixpkgs.rev}&revCount=1234>; rel=\"immutable\""
EOF
'';
in
{
name = "tarball-flakes";
nodes =
{
machine =
{ config, pkgs, ... }:
{ networking.firewall.allowedTCPPorts = [ 80 ];
nodes = {
machine =
{ config, pkgs, ... }:
{
networking.firewall.allowedTCPPorts = [ 80 ];
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
services.httpd.extraConfig = ''
ErrorLog syslog:local6
'';
services.httpd.virtualHosts."localhost" =
{ servedDirs =
[ { urlPath = "/";
dir = root;
}
];
};
virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
services.httpd.extraConfig = ''
ErrorLog syslog:local6
'';
services.httpd.virtualHosts."localhost" = {
servedDirs = [
{
urlPath = "/";
dir = root;
}
];
};
};
testScript = { nodes }: ''
# fmt: off
import json
virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [
pkgs.hello
pkgs.fuse
];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
};
};
start_all()
testScript =
{ nodes }:
''
# fmt: off
import json
machine.wait_for_unit("httpd.service")
start_all()
out = machine.succeed("nix flake metadata --json http://localhost/tags/latest.tar.gz")
print(out)
info = json.loads(out)
machine.wait_for_unit("httpd.service")
# Check that we got redirected to the immutable URL.
assert info["locked"]["url"] == "http://localhost/stable/${nixpkgs.rev}.tar.gz"
out = machine.succeed("nix flake metadata --json http://localhost/tags/latest.tar.gz")
print(out)
info = json.loads(out)
# Check that we got a fingerprint for caching.
assert info["fingerprint"]
# Check that we got redirected to the immutable URL.
assert info["locked"]["url"] == "http://localhost/stable/${nixpkgs.rev}.tar.gz"
# Check that we got the rev and revCount attributes.
assert info["revision"] == "${nixpkgs.rev}"
assert info["revCount"] == 1234
# Check that we got a fingerprint for caching.
assert info["fingerprint"]
# Check that a 0-byte HTTP 304 "Not modified" result works.
machine.succeed("nix flake metadata --refresh --json http://localhost/tags/latest.tar.gz")
# Check that we got the rev and revCount attributes.
assert info["revision"] == "${nixpkgs.rev}"
assert info["revCount"] == 1234
# Check that fetching with rev/revCount/narHash succeeds.
machine.succeed("nix flake metadata --json http://localhost/tags/latest.tar.gz?rev=" + info["revision"])
machine.succeed("nix flake metadata --json http://localhost/tags/latest.tar.gz?revCount=" + str(info["revCount"]))
machine.succeed("nix flake metadata --json http://localhost/tags/latest.tar.gz?narHash=" + info["locked"]["narHash"])
# Check that a 0-byte HTTP 304 "Not modified" result works.
machine.succeed("nix flake metadata --refresh --json http://localhost/tags/latest.tar.gz")
# Check that fetching fails if we provide incorrect attributes.
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?rev=493300eb13ae6fb387fbd47bf54a85915acc31c0")
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?revCount=789")
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?narHash=sha256-tbudgBSg+bHWHiHnlteNzN8TUvI80ygS9IULh4rklEw=")
'';
# Check that fetching with rev/revCount/narHash succeeds.
machine.succeed("nix flake metadata --json http://localhost/tags/latest.tar.gz?rev=" + info["revision"])
machine.succeed("nix flake metadata --json http://localhost/tags/latest.tar.gz?revCount=" + str(info["revCount"]))
machine.succeed("nix flake metadata --json http://localhost/tags/latest.tar.gz?narHash=" + info["locked"]["narHash"])
# Check that fetching fails if we provide incorrect attributes.
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?rev=493300eb13ae6fb387fbd47bf54a85915acc31c0")
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?revCount=789")
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?narHash=sha256-tbudgBSg+bHWHiHnlteNzN8TUvI80ygS9IULh4rklEw=")
'';
}

View file

@ -3,12 +3,15 @@
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
attacker = pkgs.runCommandWith {
name = "attacker";
stdenv = pkgs.pkgsStatic.stdenv;
} ''
$CC -static -o $out ${./attacker.c}
'';
attacker =
pkgs.runCommandWith
{
name = "attacker";
stdenv = pkgs.pkgsStatic.stdenv;
}
''
$CC -static -o $out ${./attacker.c}
'';
try-open-build-dir = pkgs.writeScript "try-open-build-dir" ''
export PATH=${pkgs.coreutils}/bin:$PATH
@ -55,75 +58,88 @@ in
name = "sandbox-setuid-leak";
nodes.machine =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
{
config,
lib,
pkgs,
...
}:
{
virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
nix.nrBuildUsers = 1;
virtualisation.additionalPaths = [ pkgs.busybox-sandbox-shell attacker try-open-build-dir create-hello-world pkgs.socat ];
virtualisation.additionalPaths = [
pkgs.busybox-sandbox-shell
attacker
try-open-build-dir
create-hello-world
pkgs.socat
];
boot.kernelPackages = pkgs.linuxPackages_latest;
users.users.alice = {
isNormalUser = true;
};
};
testScript = { nodes }: ''
start_all()
testScript =
{ nodes }:
''
start_all()
with subtest("A builder can't give access to its build directory"):
# Make sure that a builder can't change the permissions on its build
# directory to the point of opening it up to external users
with subtest("A builder can't give access to its build directory"):
# Make sure that a builder can't change the permissions on its build
# directory to the point of opening it up to external users
# A derivation whose builder tries to make its build directory as open
# as possible and wait for someone to hijack it
machine.succeed(r"""
nix-build -v -E '
builtins.derivation {
name = "open-build-dir";
system = builtins.currentSystem;
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ (builtins.storePath "${try-open-build-dir}") ];
}' >&2 &
""".strip())
# A derivation whose builder tries to make its build directory as open
# as possible and wait for someone to hijack it
machine.succeed(r"""
nix-build -v -E '
builtins.derivation {
name = "open-build-dir";
system = builtins.currentSystem;
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ (builtins.storePath "${try-open-build-dir}") ];
}' >&2 &
""".strip())
# Wait for the build to be ready
# This is OK because it runs as root, so we can access everything
machine.wait_for_file("/tmp/nix-build-open-build-dir.drv-0/build/syncPoint")
# Wait for the build to be ready
# This is OK because it runs as root, so we can access everything
machine.wait_for_file("/tmp/nix-build-open-build-dir.drv-0/build/syncPoint")
# But Alice shouldn't be able to access the build directory
machine.fail("su alice -c 'ls /tmp/nix-build-open-build-dir.drv-0/build'")
machine.fail("su alice -c 'touch /tmp/nix-build-open-build-dir.drv-0/build/bar'")
machine.fail("su alice -c 'cat /tmp/nix-build-open-build-dir.drv-0/build/foo'")
# But Alice shouldn't be able to access the build directory
machine.fail("su alice -c 'ls /tmp/nix-build-open-build-dir.drv-0/build'")
machine.fail("su alice -c 'touch /tmp/nix-build-open-build-dir.drv-0/build/bar'")
machine.fail("su alice -c 'cat /tmp/nix-build-open-build-dir.drv-0/build/foo'")
# Tell the user to finish the build
machine.succeed("echo foo > /tmp/nix-build-open-build-dir.drv-0/build/syncPoint")
# Tell the user to finish the build
machine.succeed("echo foo > /tmp/nix-build-open-build-dir.drv-0/build/syncPoint")
with subtest("Being able to execute stuff as the build user doesn't give access to the build dir"):
machine.succeed(r"""
nix-build -E '
builtins.derivation {
name = "innocent";
system = builtins.currentSystem;
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ (builtins.storePath "${create-hello-world}") ];
}' >&2 &
""".strip())
machine.wait_for_file("/tmp/nix-build-innocent.drv-0/build/syncPoint")
with subtest("Being able to execute stuff as the build user doesn't give access to the build dir"):
machine.succeed(r"""
nix-build -E '
builtins.derivation {
name = "innocent";
system = builtins.currentSystem;
builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
args = [ (builtins.storePath "${create-hello-world}") ];
}' >&2 &
""".strip())
machine.wait_for_file("/tmp/nix-build-innocent.drv-0/build/syncPoint")
# The build ran as `nixbld1` (which is the only build user on the
# machine), but a process running as `nixbld1` outside the sandbox
# shouldn't be able to touch the build directory regardless
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'ls /tmp/nix-build-innocent.drv-0/build'")
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'echo pwned > /tmp/nix-build-innocent.drv-0/build/result'")
# The build ran as `nixbld1` (which is the only build user on the
# machine), but a process running as `nixbld1` outside the sandbox
# shouldn't be able to touch the build directory regardless
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'ls /tmp/nix-build-innocent.drv-0/build'")
machine.fail("su nixbld1 --shell ${pkgs.busybox-sandbox-shell}/bin/sh -c 'echo pwned > /tmp/nix-build-innocent.drv-0/build/result'")
# Finish the build
machine.succeed("echo foo > /tmp/nix-build-innocent.drv-0/build/syncPoint")
# Finish the build
machine.succeed("echo foo > /tmp/nix-build-innocent.drv-0/build/syncPoint")
# Check that the build was not affected
machine.succeed(r"""
cat ./result
test "$(cat ./result)" = "hello, world"
""".strip())
'';
# Check that the build was not affected
machine.succeed(r"""
cat ./result
test "$(cat ./result)" = "hello, world"
""".strip())
'';
}