aboutsummaryrefslogtreecommitdiff
path: root/tests/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'tests/nixos')
-rw-r--r--tests/nixos/containers/containers.nix63
-rw-r--r--tests/nixos/containers/id-test.nix8
-rw-r--r--tests/nixos/containers/systemd-nspawn.nix78
-rw-r--r--tests/nixos/github-flakes.nix203
-rw-r--r--tests/nixos/nix-copy-closure.nix80
-rw-r--r--tests/nixos/nss-preload.nix121
-rw-r--r--tests/nixos/remote-builds.nix106
-rw-r--r--tests/nixos/setuid.nix125
-rw-r--r--tests/nixos/sourcehut-flakes.nix161
9 files changed, 945 insertions, 0 deletions
diff --git a/tests/nixos/containers/containers.nix b/tests/nixos/containers/containers.nix
new file mode 100644
index 000000000..c8ee78a4a
--- /dev/null
+++ b/tests/nixos/containers/containers.nix
@@ -0,0 +1,63 @@
+# Test whether we can run a NixOS container inside a Nix build using systemd-nspawn.
+{ lib, nixpkgs, ... }:
+
+{
+ 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 = nix-command auto-allocate-uids cgroups
+ extra-system-features = uid-range
+ '';
+ nix.nixPath = [ "nixpkgs=${nixpkgs}" ];
+ };
+ };
+
+ testScript = { nodes }: ''
+ start_all()
+
+ host.succeed("nix --version >&2")
+
+ # Test that 'id' gives the expected result in various configurations.
+
+ # Existing UIDs, sandbox.
+ host.succeed("nix build -v --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 -v --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 -v --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 -v --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 -v --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 -v --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 -v --auto-allocate-uids --sandbox -L --offline --impure --file ${./systemd-nspawn.nix} --argstr nixpkgs ${nixpkgs}")
+ host.succeed("[[ $(cat ./result/msg) = 'Hello World' ]]")
+ '';
+
+}
diff --git a/tests/nixos/containers/id-test.nix b/tests/nixos/containers/id-test.nix
new file mode 100644
index 000000000..8eb9d38f9
--- /dev/null
+++ b/tests/nixos/containers/id-test.nix
@@ -0,0 +1,8 @@
+{ name, uidRange ? false }:
+
+with import <nixpkgs> {};
+
+runCommand name
+ { requiredSystemFeatures = if uidRange then ["uid-range"] else [];
+ }
+ "id; id > $out"
diff --git a/tests/nixos/containers/systemd-nspawn.nix b/tests/nixos/containers/systemd-nspawn.nix
new file mode 100644
index 000000000..424436b3f
--- /dev/null
+++ b/tests/nixos/containers/systemd-nspawn.nix
@@ -0,0 +1,78 @@
+{ nixpkgs }:
+
+let
+
+ machine = { config, pkgs, ... }:
+ {
+ system.stateVersion = "22.05";
+ boot.isContainer = true;
+ systemd.services.console-getty.enable = false;
+ networking.dhcpcd.enable = false;
+
+ services.httpd = {
+ enable = true;
+ adminAddr = "nixos@example.org";
+ };
+
+ systemd.services.test = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "httpd.service" ];
+ script = ''
+ source /.env
+ echo "Hello World" > $out/msg
+ ls -lR /dev > $out/dev
+ ${pkgs.curl}/bin/curl -sS --fail http://localhost/ > $out/page.html
+ '';
+ unitConfig = {
+ FailureAction = "exit-force";
+ FailureActionExitStatus = 42;
+ SuccessAction = "exit-force";
+ };
+ };
+ };
+
+ cfg = (import (nixpkgs + "/nixos/lib/eval-config.nix") {
+ modules = [ machine ];
+ system = "x86_64-linux";
+ });
+
+ config = cfg.config;
+
+in
+
+with cfg._module.args.pkgs;
+
+runCommand "test"
+ { buildInputs = [ config.system.path ];
+ requiredSystemFeatures = [ "uid-range" ];
+ toplevel = config.system.build.toplevel;
+ }
+ ''
+ root=$(pwd)/root
+ mkdir -p $root $root/etc
+
+ export > $root/.env
+
+ # Make /run a tmpfs to shut up a systemd warning.
+ mkdir /run
+ mount -t tmpfs none /run
+ chmod 0700 /run
+
+ mount -t cgroup2 none /sys/fs/cgroup
+
+ mkdir -p $out
+
+ touch /etc/os-release
+ echo a5ea3f98dedc0278b6f3cc8c37eeaeac > /etc/machine-id
+
+ SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=1 \
+ ${config.systemd.package}/bin/systemd-nspawn \
+ --keep-unit \
+ -M ${config.networking.hostName} -D "$root" \
+ --register=no \
+ --resolv-conf=off \
+ --bind-ro=/nix/store \
+ --bind=$out \
+ --private-network \
+ $toplevel/init
+ ''
diff --git a/tests/nixos/github-flakes.nix b/tests/nixos/github-flakes.nix
new file mode 100644
index 000000000..e4d347691
--- /dev/null
+++ b/tests/nixos/github-flakes.nix
@@ -0,0 +1,203 @@
+{ 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
+
+ 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
+ '';
+
+ registry = pkgs.writeTextFile {
+ name = "registry";
+ text = ''
+ {
+ "flakes": [
+ {
+ "from": {
+ "type": "indirect",
+ "id": "nixpkgs"
+ },
+ "to": {
+ "type": "github",
+ "owner": "NixOS",
+ "repo": "nixpkgs"
+ }
+ },
+ {
+ "from": {
+ "type": "indirect",
+ "id": "private-flake"
+ },
+ "to": {
+ "type": "github",
+ "owner": "fancy-enterprise",
+ "repo": "private-flake"
+ }
+ }
+ ],
+ "version": 2
+ }
+ '';
+ destination = "/flake-registry.json";
+ };
+
+ private-flake-rev = "9f1dd0df5b54a7dc75b618034482ed42ce34383d";
+
+ 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}"}' > $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
+ '';
+
+ 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}"}' > $out/commits/HEAD
+ '';
+
+ 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 -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 ];
+
+ 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;
+ }
+ ];
+ };
+ };
+
+ 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 = "experimental-features = nix-command flakes";
+ networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} =
+ [ "channels.nixos.org" "api.github.com" "github.com" ];
+ security.pki.certificateFiles = [ "${cert}/ca.crt" ];
+ };
+ };
+
+ testScript = { nodes }: ''
+ # fmt: off
+ import json
+ import time
+
+ start_all()
+
+ def cat_log():
+ github.succeed("cat /var/log/httpd/*.log >&2")
+
+ github.wait_for_unit("httpd.service")
+
+ 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()
+
+ # 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()
+
+ # ... 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()
+
+ 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")
+
+ 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")
+ '';
+
+}
diff --git a/tests/nixos/nix-copy-closure.nix b/tests/nixos/nix-copy-closure.nix
new file mode 100644
index 000000000..66cbfb033
--- /dev/null
+++ b/tests/nixos/nix-copy-closure.nix
@@ -0,0 +1,80 @@
+# Test ‘nix-copy-closure’.
+
+{ lib, config, nixpkgs, hostPkgs, ... }:
+
+let
+ pkgs = config.nodes.client.nixpkgs.pkgs;
+
+ pkgA = pkgs.cowsay;
+ pkgB = pkgs.wget;
+ pkgC = pkgs.hello;
+ pkgD = pkgs.tmux;
+
+in {
+ name = "nix-copy-closure";
+
+ 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 ];
+ };
+ };
+
+ testScript = { nodes }: ''
+ # fmt: off
+ import subprocess
+
+ 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)
+
+ 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'")
+
+ # 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 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 C via the SSH substituter.
+ client.fail("nix-store -r ${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}")
+ '';
+}
diff --git a/tests/nixos/nss-preload.nix b/tests/nixos/nss-preload.nix
new file mode 100644
index 000000000..cef62e95b
--- /dev/null
+++ b/tests/nixos/nss-preload.nix
@@ -0,0 +1,121 @@
+{ lib, config, nixpkgs, ... }:
+
+let
+
+ pkgs = config.nodes.client.nixpkgs.pkgs;
+
+ nix-fetch = pkgs.writeText "fetch.nix" ''
+ derivation {
+ # This derivation is an copy from what is available over at
+ # nix.git:corepkgs/fetchurl.nix
+ builder = "builtin:fetchurl";
+
+ # We're going to fetch data from the http_dns instance created before
+ # we expect the content to be the same as the content available there.
+ # ```
+ # $ nix-hash --type sha256 --to-base32 $(echo "hello world" | sha256sum | cut -d " " -f 1)
+ # 0ix4jahrkll5zg01wandq78jw3ab30q4nscph67rniqg5x7r0j59
+ # ```
+ outputHash = "0ix4jahrkll5zg01wandq78jw3ab30q4nscph67rniqg5x7r0j59";
+ outputHashAlgo = "sha256";
+ outputHashMode = "flat";
+
+ name = "example.com";
+ url = "http://example.com";
+
+ unpack = false;
+ executable = false;
+
+ system = "builtin";
+
+ preferLocalBuild = true;
+
+ impureEnvVars = [
+ "http_proxy" "https_proxy" "ftp_proxy" "all_proxy" "no_proxy"
+ ];
+
+ urls = [ "http://example.com" ];
+ }
+ '';
+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; }
+ ];
+
+ 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"
+ '';
+ };
+ };
+ };
+
+ # client consumes a remote resolver
+ client = { lib, nodes, pkgs, ... }: {
+ networking.useDHCP = false;
+ networking.nameservers = [
+ (lib.head nodes.http_dns.config.networking.interfaces.eth1.ipv6.addresses).address
+ (lib.head nodes.http_dns.config.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;
+ };
+ };
+
+ 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)
+
+ client.start()
+ client.wait_for_unit('multi-user.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("nix-build can lookup dns and fetch data"):
+ client.succeed("""
+ nix-build ${nix-fetch} >&2
+ """)
+ '';
+}
diff --git a/tests/nixos/remote-builds.nix b/tests/nixos/remote-builds.nix
new file mode 100644
index 000000000..696cd2652
--- /dev/null
+++ b/tests/nixos/remote-builds.nix
@@ -0,0 +1,106 @@
+# Test Nix's remote build feature.
+
+{ config, lib, hostPkgs, ... }:
+
+let
+ pkgs = config.nodes.client.nixpkgs.pkgs;
+
+ # The configuration of the remote builders.
+ builder =
+ { config, pkgs, ... }:
+ { services.openssh.enable = true;
+ virtualisation.writableStore = true;
+ nix.settings.sandbox = true;
+ };
+
+ # Trivial Nix expression to build remotely.
+ expr = config: nr: pkgs.writeText "expr.nix"
+ ''
+ let utils = builtins.storePath ${config.system.build.extraUtils}; in
+ derivation {
+ name = "hello-${toString nr}";
+ system = "i686-linux";
+ PATH = "''${utils}/bin";
+ builder = "''${utils}/bin/sh";
+ args = [ "-c" "if [ ${toString nr} = 5 ]; then echo FAIL; exit 1; fi; echo Hello; mkdir $out $foo; cat /proc/sys/kernel/hostname > $out/host; ln -s $out $foo/bar; sleep 10" ];
+ outputs = [ "out" "foo" ];
+ }
+ '';
+
+in
+
+{
+ name = "remote-builds";
+
+ 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";
+ };
+ };
+
+ testScript = { nodes }: ''
+ # fmt: off
+ import subprocess
+
+ 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")
+
+ # 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")
+ client.succeed(f"ssh -o StrictHostKeyChecking=no {builder.name} 'echo hello world'")
+
+ # Perform a build and check that it was performed on the builder.
+ out = client.succeed(
+ "nix-build ${expr nodes.client.config 1} 2> build-output",
+ "grep -q Hello build-output"
+ )
+ builder1.succeed(f"test -e {out}")
+
+ # And a parallel build.
+ paths = client.succeed(r'nix-store -r $(nix-instantiate ${expr nodes.client.config 2})\!out $(nix-instantiate ${expr nodes.client.config 3})\!out')
+ out1, out2 = paths.split()
+ builder1.succeed(f"test -e {out1} -o -e {out2}")
+ builder2.succeed(f"test -e {out1} -o -e {out2}")
+
+ # And a failing build.
+ client.fail("nix-build ${expr nodes.client.config 5}")
+
+ # Test whether the build hook automatically skips unavailable builders.
+ builder1.block()
+ client.succeed("nix-build ${expr nodes.client.config 4}")
+ '';
+}
diff --git a/tests/nixos/setuid.nix b/tests/nixos/setuid.nix
new file mode 100644
index 000000000..2b66320dd
--- /dev/null
+++ b/tests/nixos/setuid.nix
@@ -0,0 +1,125 @@
+# Verify that Linux builds cannot create setuid or setgid binaries.
+
+{ lib, config, nixpkgs, ... }:
+
+let
+ pkgs = config.nodes.machine.nixpkgs.pkgs;
+
+in
+{
+ name = "setuid";
+
+ nodes.machine =
+ { 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 ];
+ };
+
+ 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())
+
+ machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
+
+ 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())
+
+ machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
+
+ 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())
+
+ machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
+
+ 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())
+
+ machine.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
+
+ 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())
+
+ machine.succeed('[[ $(stat -c %a /tmp/id) = 666 ]]')
+
+ 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.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
+
+ 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())
+
+ machine.succeed('[[ $(stat -c %a /tmp/id) = 1750 ]]')
+
+ 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.succeed('[[ $(stat -c %a /tmp/id) = 555 ]]')
+
+ machine.succeed("rm /tmp/id")
+ '';
+}
diff --git a/tests/nixos/sourcehut-flakes.nix b/tests/nixos/sourcehut-flakes.nix
new file mode 100644
index 000000000..a76fed020
--- /dev/null
+++ b/tests/nixos/sourcehut-flakes.nix
@@ -0,0 +1,161 @@
+{ 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
+
+ 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
+ '';
+
+ registry = pkgs.writeTextFile {
+ name = "registry";
+ text = ''
+ {
+ "flakes": [
+ {
+ "from": {
+ "type": "indirect",
+ "id": "nixpkgs"
+ },
+ "to": {
+ "type": "sourcehut",
+ "owner": "~NixOS",
+ "repo": "nixpkgs"
+ }
+ }
+ ],
+ "version": 2
+ }
+ '';
+ destination = "/flake-registry.json";
+ };
+
+ 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 -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
+
+ 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
+ '';
+
+in
+
+ {
+ name = "sourcehut-flakes";
+
+ nodes =
+ {
+ # Impersonate git.sr.ht
+ sourcehut =
+ { 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."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 = ''
+ experimental-features = nix-command flakes
+ flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json
+ '';
+ environment.systemPackages = [ pkgs.jq ];
+ networking.hosts.${(builtins.head nodes.sourcehut.config.networking.interfaces.eth1.ipv4.addresses).address} =
+ [ "git.sr.ht" ];
+ security.pki.certificateFiles = [ "${cert}/ca.crt" ];
+ };
+ };
+
+ testScript = { nodes }: ''
+ # fmt: off
+ import json
+ import time
+
+ start_all()
+
+ sourcehut.wait_for_unit("httpd.service")
+
+ client.succeed("curl -v https://git.sr.ht/ >&2")
+ client.succeed("nix registry list | grep nixpkgs")
+
+ # Test that it resolves HEAD
+ rev = client.succeed("nix flake info sourcehut:~NixOS/nixpkgs --json | jq -r .revision")
+ assert rev.strip() == "${nixpkgs.rev}", "revision mismatch"
+ # Test that it resolves branches
+ rev = client.succeed("nix flake info sourcehut:~NixOS/nixpkgs/master --json | jq -r .revision")
+ assert rev.strip() == "${nixpkgs.rev}", "revision mismatch"
+ # Test that it resolves tags
+ rev = client.succeed("nix flake info sourcehut:~NixOS/nixpkgs/foo-bar --json | jq -r .revision")
+ assert rev.strip() == "${nixpkgs.rev}", "revision mismatch"
+
+ # Registry and pinning test
+ rev = client.succeed("nix flake info nixpkgs --json | jq -r .revision")
+ assert rev.strip() == "${nixpkgs.rev}", "revision mismatch"
+
+ client.succeed("nix registry pin nixpkgs")
+
+ client.succeed("nix flake info nixpkgs --tarball-ttl 0 >&2")
+
+ # Shut down the web server. The flake should be cached on the client.
+ sourcehut.succeed("systemctl stop httpd.service")
+
+ info = json.loads(client.succeed("nix flake info 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")
+ '';
+
+}