From a536a1a970a6455dc17f89e248acacfd50cfd9a8 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 7 Jan 2021 12:50:31 -0500 Subject: [PATCH] First passing end-to-end test of SFTP --- integration/conftest.py | 37 +++++++++++++++++++++++++++++++++---- integration/test_sftp.py | 37 +++++++++++++++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 integration/test_sftp.py diff --git a/integration/conftest.py b/integration/conftest.py index cc4ffaa08..28e364cb6 100644 --- a/integration/conftest.py +++ b/integration/conftest.py @@ -8,6 +8,7 @@ from os.path import join, exists from tempfile import mkdtemp, mktemp from functools import partial from json import loads +from subprocess import check_call from foolscap.furl import ( decode_furl, @@ -342,6 +343,12 @@ def storage_nodes(reactor, temp_dir, introducer, introducer_furl, flog_gatherer, return nodes +def generate_ssh_key(path): + """Create a new SSH private/public key pair.""" + check_call(["ckeygen", "--type", "rsa", "--no-passphrase", "--bits", "512", + "--file", path]) + + @pytest.fixture(scope='session') @log_call(action_type=u"integration:alice", include_args=[], include_result=False) def alice(reactor, temp_dir, introducer_furl, flog_gatherer, storage_nodes, request): @@ -353,14 +360,36 @@ def alice(reactor, temp_dir, introducer_furl, flog_gatherer, storage_nodes, requ ) ) await_client_ready(process) + + # 1. Create a new RW directory cap: cli(process, "create-alias", "test") rwcap = loads(cli(process, "list-aliases", "--json"))["test"]["readwrite"] - # TODO at this point we need to: - # 1. configure sftpd - # 2. add an sftp access file with username, password, and rwcap - # 3. eventually, add sftp access with public key + + # 2. Enable SFTP on the node: + ssh_key_path = join(process.node_dir, "private", "ssh_host_rsa_key") + accounts_path = join(process.node_dir, "private", "accounts") + with open(join(process.node_dir, "tahoe.cfg"), "a") as f: + f.write("""\ +[sftpd] +enabled = true +port = tcp:8022:interface=127.0.0.1 +host_pubkey_file = {ssh_key_path}.pub +host_privkey_file = {ssh_key_path} +accounts.file = {accounts_path} +""".format(ssh_key_path=ssh_key_path, accounts_path=accounts_path)) + generate_ssh_key(ssh_key_path) + + # 3. Add a SFTP access file with username, password, and rwcap. + with open(accounts_path, "w") as f: + f.write("""\ +alice password {} +""".format(rwcap)) + # TODO add sftp access with public key + + # 4. Restart the node with new SFTP config. process.kill() pytest_twisted.blockon(_run_node(reactor, process.node_dir, request, None)) + await_client_ready(process) return process diff --git a/integration/test_sftp.py b/integration/test_sftp.py new file mode 100644 index 000000000..f308ddf5a --- /dev/null +++ b/integration/test_sftp.py @@ -0,0 +1,37 @@ +""" +It's possible to create/rename/delete files and directories in Tahoe-LAFS using +SFTP. +""" + +from __future__ import unicode_literals +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + +from paramiko import SSHClient +from paramiko.client import AutoAddPolicy +from paramiko.sftp_client import SFTPClient + + +def test_read_write_files(alice): + """It's possible to upload and download files.""" + client = SSHClient() + client.set_missing_host_key_policy(AutoAddPolicy) + client.connect( + "localhost", username="alice", password="password", port=8022, + look_for_keys=False + ) + sftp = SFTPClient.from_transport(client.get_transport()) + f = sftp.file("myfile", "wb") + f.write(b"abc") + f.write(b"def") + f.close() + f = sftp.file("myfile", "rb") + assert f.read(4) == b"abcd" + assert f.read(2) == b"ef" + assert f.read(1) == b"" + f.close() diff --git a/setup.py b/setup.py index 0e5a43dba..32581e293 100644 --- a/setup.py +++ b/setup.py @@ -399,6 +399,7 @@ setup(name="tahoe-lafs", # also set in __init__.py "html5lib", "junitxml", "tenacity", + "paramiko", ] + tor_requires + i2p_requires, "tor": tor_requires, "i2p": i2p_requires,