nixos/test-driver: add backdoor based on systemd-ssh-proxy & AF_VSOCK (#392030)

This commit is contained in:
Jacek Galowicz 2025-05-09 08:03:55 +02:00 committed by GitHub
commit 8b3baa1402
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 100 additions and 4 deletions

View File

@ -63,6 +63,35 @@ using:
Once the connection is established, you can enter commands in the socat terminal Once the connection is established, you can enter commands in the socat terminal
where socat is running. where socat is running.
## SSH Access for test machines {#sec-nixos-test-ssh-access}
An SSH-based backdoor to log into machines can be enabled with
```nix
{
name = "…";
nodes.machines = { /* … */ };
sshBackdoor.enable = true;
}
```
This creates a [vsock socket](https://man7.org/linux/man-pages/man7/vsock.7.html)
for each VM to log in with SSH. This configures root login with an empty password.
When the VMs get started interactively with the test-driver, it's possible to
connect to `machine` with
```
$ ssh vsock/3 -o User=root
```
The socket numbers correspond to the node number of the test VM, but start
at three instead of one because that's the lowest possible
vsock number.
On non-NixOS systems you'll probably need to enable
the SSH config from {manpage}`systemd-ssh-proxy(1)` yourself.
## Port forwarding to NixOS test VMs {#sec-nixos-test-port-forwarding} ## Port forwarding to NixOS test VMs {#sec-nixos-test-port-forwarding}
If your test has only a single VM, you may use e.g. If your test has only a single VM, you may use e.g.

View File

@ -1823,6 +1823,9 @@
"sec-test-options-reference": [ "sec-test-options-reference": [
"index.html#sec-test-options-reference" "index.html#sec-test-options-reference"
], ],
"test-opt-sshBackdoor.enable": [
"index.html#test-opt-sshBackdoor.enable"
],
"test-opt-defaults": [ "test-opt-defaults": [
"index.html#test-opt-defaults" "index.html#test-opt-defaults"
], ],
@ -1910,6 +1913,9 @@
"sec-nixos-test-shell-access": [ "sec-nixos-test-shell-access": [
"index.html#sec-nixos-test-shell-access" "index.html#sec-nixos-test-shell-access"
], ],
"sec-nixos-test-ssh-access": [
"index.html#sec-nixos-test-ssh-access"
],
"sec-nixos-test-port-forwarding": [ "sec-nixos-test-port-forwarding": [
"index.html#sec-nixos-test-port-forwarding" "index.html#sec-nixos-test-port-forwarding"
], ],

View File

@ -109,6 +109,11 @@ def main() -> None:
help="the test script to run", help="the test script to run",
type=Path, type=Path,
) )
arg_parser.add_argument(
"--dump-vsocks",
help="indicates that the interactive SSH backdoor is active and dumps information about it on start",
action="store_true",
)
args = arg_parser.parse_args() args = arg_parser.parse_args()
@ -136,6 +141,8 @@ def main() -> None:
if args.interactive: if args.interactive:
history_dir = os.getcwd() history_dir = os.getcwd()
history_path = os.path.join(history_dir, ".nixos-test-history") history_path = os.path.join(history_dir, ".nixos-test-history")
if args.dump_vsocks:
driver.dump_machine_ssh()
ptpython.ipython.embed( ptpython.ipython.embed(
user_ns=driver.test_symbols(), user_ns=driver.test_symbols(),
history_filename=history_path, history_filename=history_path,

View File

@ -11,6 +11,8 @@ from pathlib import Path
from typing import Any from typing import Any
from unittest import TestCase from unittest import TestCase
from colorama import Style
from test_driver.errors import MachineError, RequestedAssertionFailed from test_driver.errors import MachineError, RequestedAssertionFailed
from test_driver.logger import AbstractLogger from test_driver.logger import AbstractLogger
from test_driver.machine import Machine, NixStartScript, retry from test_driver.machine import Machine, NixStartScript, retry
@ -176,6 +178,19 @@ class Driver:
) )
return {**general_symbols, **machine_symbols, **vlan_symbols} return {**general_symbols, **machine_symbols, **vlan_symbols}
def dump_machine_ssh(self) -> None:
print("SSH backdoor enabled, the machines can be accessed like this:")
print(
f"{Style.BRIGHT}Note:{Style.RESET_ALL} this requires {Style.BRIGHT}systemd-ssh-proxy(1){Style.RESET_ALL} to be enabled (default on NixOS 25.05 and newer)."
)
names = [machine.name for machine in self.machines]
longest_name = len(max(names, key=len))
for num, name in enumerate(names, start=3):
spaces = " " * (longest_name - len(name) + 2)
print(
f" {name}:{spaces}{Style.BRIGHT}ssh -o User=root vsock/{num}{Style.RESET_ALL}"
)
def test_script(self) -> None: def test_script(self) -> None:
"""Run the test script""" """Run the test script"""
with self.logger.nested("run the VM test script"): with self.logger.nested("run the VM test script"):

View File

@ -75,6 +75,7 @@ pkgs.lib.throwIf (args ? specialArgs)
), ),
extraPythonPackages ? (_: [ ]), extraPythonPackages ? (_: [ ]),
interactive ? { }, interactive ? { },
sshBackdoor ? { },
}@t: }@t:
let let
testConfig = testConfig =

View File

@ -13,6 +13,7 @@ let
mapAttrs mapAttrs
mkDefault mkDefault
mkIf mkIf
mkMerge
mkOption mkOption
mkForce mkForce
optional optional
@ -77,6 +78,14 @@ in
{ {
options = { options = {
sshBackdoor = {
enable = mkOption {
default = false;
type = types.bool;
description = "Whether to turn on the VSOCK-based access to all VMs. This provides an unauthenticated access intended for debugging.";
};
};
node.type = mkOption { node.type = mkOption {
type = types.raw; type = types.raw;
default = baseOS.type; default = baseOS.type;
@ -172,10 +181,19 @@ in
passthru.nodes = config.nodesCompat; passthru.nodes = config.nodesCompat;
defaults = mkIf config.node.pkgsReadOnly { extraDriverArgs = mkIf config.sshBackdoor.enable [
nixpkgs.pkgs = config.node.pkgs; "--dump-vsocks"
imports = [ ../../modules/misc/nixpkgs/read-only.nix ]; ];
};
defaults = mkMerge [
(mkIf config.node.pkgsReadOnly {
nixpkgs.pkgs = config.node.pkgs;
imports = [ ../../modules/misc/nixpkgs/read-only.nix ];
})
(mkIf config.sshBackdoor.enable {
testing.sshBackdoor.enable = true;
})
];
}; };
} }

View File

@ -87,6 +87,10 @@ in
machine.switch_root() to leave stage 1 and proceed to stage 2 machine.switch_root() to leave stage 1 and proceed to stage 2
''; '';
sshBackdoor = {
enable = mkEnableOption "vsock-based ssh backdoor for the VM";
};
}; };
config = { config = {
@ -100,6 +104,18 @@ in
} }
]; ];
services.openssh = mkIf config.testing.sshBackdoor.enable {
enable = true;
settings = {
PermitRootLogin = "yes";
PermitEmptyPasswords = "yes";
};
};
security.pam.services.sshd = mkIf config.testing.sshBackdoor.enable {
allowNullPassword = true;
};
systemd.services.backdoor = lib.mkMerge [ systemd.services.backdoor = lib.mkMerge [
backdoorService backdoorService
{ {
@ -175,6 +191,10 @@ in
# we avoid defining attributes if not possible. # we avoid defining attributes if not possible.
# TODO: refactor such that test-instrumentation can import qemu-vm # TODO: refactor such that test-instrumentation can import qemu-vm
package = lib.mkDefault pkgs.qemu_test; package = lib.mkDefault pkgs.qemu_test;
options = mkIf config.testing.sshBackdoor.enable [
"-device vhost-vsock-pci,guest-cid=${toString (config.virtualisation.test.nodeNumber + 2)}"
];
}; };
}; };