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

138 lines
3.7 KiB
Python
Raw Normal View History

2023-08-09 14:38:08 +00:00
import os
import shutil
import string
2023-08-09 14:38:08 +00:00
import subprocess
import time
2023-11-29 11:40:48 +00:00
from collections.abc import Iterator
2023-08-09 14:38:08 +00:00
from pathlib import Path
from sys import platform
from tempfile import TemporaryDirectory
2023-11-29 11:40:48 +00:00
from typing import TYPE_CHECKING
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(test_root: Path) -> Iterator[SshdConfig]:
2023-09-27 14:13:04 +00:00
# FIXME, if any parent of the sshd directory is world-writable than sshd will refuse it.
# we use .direnv instead since it's already in .gitignore
with TemporaryDirectory() as _dir:
2023-11-30 13:13:32 +00:00
tmpdir = Path(_dir)
host_key = test_root / "data" / "ssh_host_ed25519_key"
host_key.chmod(0o600)
template = (test_root / "data" / "sshd_config").read_text()
content = string.Template(template).substitute(dict(host_key=host_key))
2023-11-30 13:13:32 +00:00
config = tmpdir / "sshd_config"
config.write_text(content)
2023-11-30 13:13:32 +00:00
login_shell = tmpdir / "shell"
2023-08-10 10:30:52 +00:00
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)`
2023-11-30 13:13:32 +00:00
lib_path = tmpdir / "libgetpwnam-preload.so"
2023-08-10 10:30:52 +00:00
subprocess.run(
[
os.environ.get("CC", "cc"),
"-shared",
"-o",
lib_path,
str(test_root / "getpwnam-preload.c"),
],
check=True,
)
yield SshdConfig(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)