diff --git a/jupyter_sshd_proxy/__init__.py b/jupyter_sshd_proxy/__init__.py index a2c777d..6ce9442 100644 --- a/jupyter_sshd_proxy/__init__.py +++ b/jupyter_sshd_proxy/__init__.py @@ -27,7 +27,7 @@ def setup_sshd() -> Dict[str, Any]: '-o', f'AuthorizedKeysFile {AUTHORIZED_KEYS_PATH}', '-o', f'LogLevel {SSHD_LOG_LEVEL}' ] - print(shlex.join(cmd)) + return { "command": cmd, "raw_socket_proxy": True, diff --git a/tests/test_ssh.py b/tests/test_ssh.py index 013c719..233ecd6 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -8,6 +8,8 @@ import getpass import time import socket +from urllib.request import urlopen, Request +from urllib.error import URLError @pytest.fixture def random_port(): @@ -29,15 +31,33 @@ def jupyter_server(random_port): '--no-browser' ] env = os.environ.copy() - with tempfile.TemporaryDirectory() as temp_dir: + + # sshd requires that the path to the authorized keys (and every ancestor) is fully owned + # by the user who is trying to log in (or root), and mode is not group or world writeable. + # Since that's not necessarily true for `/tmp`, we can not put our keys there for tests. + # Create them instead in cwd, which we assume matches this description instead. We + # clean up after ourselves. + dir_prefix = os.path.join(os.getcwd(), "tmp-") + with tempfile.TemporaryDirectory(prefix=dir_prefix) as temp_dir: + os.chmod(temp_dir, 0o700) authorized_keys_path = os.path.join(temp_dir, 'authorized_keys') subprocess.check_call(['ssh-keygen', '-f', authorized_keys_path, '-q', '-N', '']) env['JUPYTER_SSHD_PROXY_AUTHORIZED_KEYS_PATH'] = authorized_keys_path + '.pub' proc = subprocess.Popen(c, env=env) - # Should healthcheck instead but HEY - time.sleep(1) + # Wait for server to be fully up before we yield + req = Request(f"http://127.0.0.1:{random_port}/api/status", headers={"Authorization": f"token {token}"}) + while True: + try: + resp = urlopen(req) + if resp.status == 200: + break + except URLError as e: + if not isinstance(e.reason, ConnectionRefusedError): + raise + print("Waiting for jupyter server to come up...") + time.sleep(1) yield (random_port, token, authorized_keys_path) @@ -55,22 +75,25 @@ def get_ssh_client_options(random_port, token, authorized_keys_path): def test_ssh_command_execution(jupyter_server): cmd = [ - 'ssh', '-v', + 'ssh', ] + [f"-o={o}" for o in get_ssh_client_options(*jupyter_server)] + ['127.0.0.1', 'hostname'] - out = subprocess.check_output(cmd).decode().strip() + proc = subprocess.run(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + print(proc.stderr) - assert out == socket.gethostname() + assert proc.stdout.decode().strip() == socket.gethostname() def test_ssh_interactive(jupyter_server): + # Explicitly call /bin/sh without any args, so we can run without any prompts cmd = [ - 'ssh', '-v', - ] + [f"-o={o}" for o in get_ssh_client_options(*jupyter_server)] + ['127.0.0.1', 'hostname'] + 'ssh', + ] + [f"-o={o}" for o in get_ssh_client_options(*jupyter_server)] + ['127.0.0.1', '/bin/sh'] proc = pexpect.spawn(shlex.join(cmd), echo=False) proc.sendline('hostname') assert proc.readline().decode().strip() == socket.gethostname() + proc.sendline("exit") proc.wait() assert proc.exitstatus == 0