clan-core/pkgs/clan-cli/tests/sshd.py

155 lines
4.0 KiB
Python
Raw Normal View History

2023-08-09 14:38:08 +00:00
import os
import shutil
import subprocess
import time
from pathlib import Path
from sys import platform
from tempfile import TemporaryDirectory
2023-08-10 10:30:52 +00:00
from typing import TYPE_CHECKING, Iterator
2023-08-09 14:38:08 +00:00
import pytest
2023-08-10 13:35:38 +00:00
if TYPE_CHECKING:
from command import Command
from ports import PortFunction
2023-08-09 14:38:08 +00:00
class Sshd:
def __init__(self, port: int, proc: subprocess.Popen[str], key: str) -> None:
self.port = port
self.proc = proc
self.key = key
class SshdConfig:
2023-08-10 10:30:52 +00:00
def __init__(
self, path: Path, login_shell: Path, key: str, preload_lib: Path
) -> None:
2023-08-09 14:38:08 +00:00
self.path = path
2023-08-10 10:30:52 +00:00
self.login_shell = login_shell
2023-08-09 14:38:08 +00:00
self.key = key
self.preload_lib = preload_lib
@pytest.fixture(scope="session")
def sshd_config(project_root: Path, test_root: Path) -> Iterator[SshdConfig]:
# FIXME, if any parent of `project_root` is world-writable than sshd will refuse it.
with TemporaryDirectory(dir=project_root) as _dir:
dir = Path(_dir)
host_key = dir / "host_ssh_host_ed25519_key"
subprocess.run(
[
"ssh-keygen",
"-t",
"ed25519",
"-f",
host_key,
"-N",
"",
],
check=True,
)
sshd_config = dir / "sshd_config"
sshd_config.write_text(
f"""
HostKey {host_key}
LogLevel DEBUG3
# In the nix build sandbox we don't get any meaningful PATH after login
MaxStartups 64:30:256
AuthorizedKeysFile {host_key}.pub
2023-08-10 10:30:52 +00:00
AcceptEnv REALPATH
2023-09-21 15:17:48 +00:00
PasswordAuthentication no
2023-08-09 14:38:08 +00:00
"""
)
2023-08-10 10:30:52 +00:00
login_shell = dir / "shell"
bash = shutil.which("bash")
path = os.environ["PATH"]
assert bash is not None
login_shell.write_text(
f"""#!{bash}
if [[ -f /etc/profile ]]; then
source /etc/profile
fi
if [[ -n "$REALPATH" ]]; then
export PATH="$REALPATH:${path}"
else
export PATH="${path}"
fi
exec {bash} -l "${{@}}"
"""
)
login_shell.chmod(0o755)
2023-08-09 14:38:08 +00:00
lib_path = None
2023-08-10 10:30:52 +00:00
assert (
platform == "linux"
), "we do not support the ld_preload trick on non-linux just now"
2023-08-09 14:38:08 +00:00
2023-08-10 10:30:52 +00:00
# This enforces a login shell by overriding the login shell of `getpwnam(3)`
lib_path = dir / "libgetpwnam-preload.so"
subprocess.run(
[
os.environ.get("CC", "cc"),
"-shared",
"-o",
lib_path,
str(test_root / "getpwnam-preload.c"),
],
check=True,
)
yield SshdConfig(sshd_config, login_shell, str(host_key), lib_path)
2023-08-09 14:38:08 +00:00
@pytest.fixture
def sshd(
sshd_config: SshdConfig,
command: "Command",
unused_tcp_port: "PortFunction",
monkeypatch: pytest.MonkeyPatch,
) -> Iterator[Sshd]:
import subprocess
port = unused_tcp_port()
2023-08-09 14:38:08 +00:00
sshd = shutil.which("sshd")
assert sshd is not None, "no sshd binary found"
env = {}
2023-08-10 10:30:52 +00:00
env = dict(
LD_PRELOAD=str(sshd_config.preload_lib),
LOGIN_SHELL=str(sshd_config.login_shell),
)
2023-08-09 14:38:08 +00:00
proc = command.run(
2023-08-10 10:30:52 +00:00
[sshd, "-f", str(sshd_config.path), "-D", "-p", str(port)], extra_env=env
2023-08-09 14:38:08 +00:00
)
monkeypatch.delenv("SSH_AUTH_SOCK", raising=False)
2023-08-09 14:38:08 +00:00
while True:
2023-09-21 15:17:48 +00:00
print(sshd_config.path)
2023-08-09 14:38:08 +00:00
if (
subprocess.run(
[
"ssh",
"-o",
"StrictHostKeyChecking=no",
"-o",
"UserKnownHostsFile=/dev/null",
"-i",
sshd_config.key,
"localhost",
"-p",
str(port),
"true",
2023-09-21 15:17:48 +00:00
],
2023-08-09 14:38:08 +00:00
).returncode
== 0
):
yield Sshd(port, proc, sshd_config.key)
return
else:
rc = proc.poll()
if rc is not None:
raise Exception(f"sshd processes was terminated with {rc}")
time.sleep(0.1)