From abfd91a44551cda4ef11f1a44b82e27e47f3908b Mon Sep 17 00:00:00 2001 From: Ryo Nakamura Date: Sun, 14 Apr 2024 15:49:27 +0900 Subject: [PATCH] add -J proxyjump option (#15) --- doc/mscp.1.in | 16 ++++++++++++++++ doc/mscp.rst | 11 ++++++++--- include/mscp.h | 1 + scripts/test-in-container.sh | 3 +++ src/main.c | 12 ++++++++---- src/ssh.c | 10 ++++++++++ test/test_e2e.py | 25 +++++++++++++++++++++++++ 7 files changed, 71 insertions(+), 7 deletions(-) diff --git a/doc/mscp.1.in b/doc/mscp.1.in index 4d3184e..c180a13 100644 --- a/doc/mscp.1.in +++ b/doc/mscp.1.in @@ -56,6 +56,9 @@ mscp \- copy files over multiple SSH connections .BI \-i \ IDENTITY\c ] [\c +.BI \-J \ DESTINATION\c +] +[\c .BI \-c \ CIPHER\c ] [\c @@ -270,6 +273,19 @@ supports. .B \-i \fIIDENTITY\fR Specifies the identity file for public key authentication. +.TP +.B \-J \fIDESTINATION\fR +A shortcut to define a +.B ProxyJump +configuration directive. Each SFTP session of +.B mscp +connects to the target host by first making an +.B ssh +connection to the jump host described by +.I destination. + + + .TP .B \-c \fICIPHER\fR Selects the cipher to use for encrypting the data transfer. See diff --git a/doc/mscp.rst b/doc/mscp.rst index 9935d21..213e9c5 100644 --- a/doc/mscp.rst +++ b/doc/mscp.rst @@ -2,7 +2,7 @@ MSCP ==== -:Date: v0.1.5-16-ga1ba6f1 +:Date: v0.1.5-18-ge47d5b7 NAME ==== @@ -18,8 +18,8 @@ SYNOPSIS **-S** *MAX_CHUNK_SIZE* ] [ **-a** *NR_AHEAD* ] [ **-b** *BUF_SIZE* ] [ **-L** *LIMIT_BITRATE* ] [ **-l** *LOGIN_NAME* ] [ **-P** *PORT* ] [ **-F** *SSH_CONFIG* ] [ **-o** *SSH_OPTION* ] [ **-i** *IDENTITY* ] [ -**-c** *CIPHER* ] [ **-M** *HMAC* ] [ **-C** *COMPRESS* ] [ **-g** -*CONGESTION* ] *source ... target* +**-J** *DESTINATION* ] [ **-c** *CIPHER* ] [ **-M** *HMAC* ] [ **-C** +*COMPRESS* ] [ **-g** *CONGESTION* ] *source ... target* DESCRIPTION =========== @@ -159,6 +159,11 @@ OPTIONS **-i IDENTITY** Specifies the identity file for public key authentication. +**-J DESTINATION** + A shortcut to define a **ProxyJump** configuration directive. Each + SFTP session of **mscp** connects to the target host by first making + an **ssh** connection to the jump host described by *destination.* + **-c CIPHER** Selects the cipher to use for encrypting the data transfer. See `libssh features `__. diff --git a/include/mscp.h b/include/mscp.h index fa8af65..8519f23 100644 --- a/include/mscp.h +++ b/include/mscp.h @@ -63,6 +63,7 @@ struct mscp_ssh_opts { char *config; /** path to ssh_config, default ~/.ssh/config*/ char **options; /** array of ssh_config options, terminated by NULL */ char *identity; /** path to private key */ + char *proxyjump; /** ProxyJump configuration directive (shortcut) */ char *cipher; /** cipher spec */ char *hmac; /** hmacp spec */ char *compress; /** yes, no, zlib@openssh.com */ diff --git a/scripts/test-in-container.sh b/scripts/test-in-container.sh index 21a9969..d0ab42f 100755 --- a/scripts/test-in-container.sh +++ b/scripts/test-in-container.sh @@ -12,6 +12,9 @@ set -x echo "Port 22" >> /etc/ssh/sshd_config echo "Port 8022" >> /etc/ssh/sshd_config +## Alpine default sshd disables TcpForwarding, which is required for proxyjump test +echo "AllowTcpForwarding yes" >> /etc/ssh/sshd_config.d/10-accept-tcp-forwarding.conf + # Run sshd if [ ! -e /var/run/sshd.pid ]; then /usr/sbin/sshd diff --git a/src/main.c b/src/main.c index ae163e5..de53e30 100644 --- a/src/main.c +++ b/src/main.c @@ -24,12 +24,12 @@ void usage(bool print_help) { printf("mscp " MSCP_BUILD_VERSION ": copy files over multiple SSH connections\n" "\n" - "Usage: mscp [-46vqDpdNh] [-n nr_conns] [-m coremask]\n" - " [-u max_startups] [-I interval] [-W checkpoint] [-R checkpoint]\n" + "Usage: mscp [-46vqDpdNh] [-n nr_conns] [-m coremask] [-u max_startups]\n" + " [-I interval] [-W checkpoint] [-R checkpoint]\n" " [-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead]\n" " [-b buf_sz] [-L limit_bitrate]\n" " [-l login_name] [-P port] [-F ssh_config] [-o ssh_option]\n" - " [-i identity_file] [-c cipher_spec] [-M hmac_spec]\n" + " [-i identity_file] [-J destination] [-c cipher_spec] [-M hmac_spec]\n" " [-C compress] [-g congestion]\n" " source ... target\n" "\n"); @@ -64,6 +64,7 @@ void usage(bool print_help) " -F SSH_CONFIG path to user ssh config (default ~/.ssh/config)\n" " -o SSH_OPTION ssh_config option\n" " -i IDENTITY identity file for public key authentication\n" + " -J DESTINATION ProxyJump destination\n" " -c CIPHER cipher spec\n" " -M HMAC hmac spec\n" " -C COMPRESS enable compression: " @@ -277,7 +278,7 @@ int main(int argc, char **argv) memset(&o, 0, sizeof(o)); o.severity = MSCP_SEVERITY_WARN; -#define mscpopts "n:m:u:I:W:R:s:S:a:b:L:46vqDrl:P:i:F:o:c:M:C:g:pdNh" +#define mscpopts "n:m:u:I:W:R:s:S:a:b:L:46vqDrl:P:F:o:i:J:c:M:C:g:pdNh" while ((ch = getopt(argc, argv, mscpopts)) != -1) { switch (ch) { case 'n': @@ -375,6 +376,9 @@ int main(int argc, char **argv) case 'i': s.identity = optarg; break; + case 'J': + s.proxyjump = optarg; + break; case 'c': s.cipher = optarg; break; diff --git a/src/ssh.c b/src/ssh.c index 7c9e94e..d647c4b 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -88,6 +88,16 @@ static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts) return -1; } + if (opts->proxyjump) { + char buf[256]; + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), "proxyjump=%s", opts->proxyjump); + if (ssh_config_parse_string(ssh, buf) != SSH_OK) { + priv_set_errv("failed to set ssh option: %s", buf); + return -1; + } + } + if (opts->options) { int n; for (n = 0; opts->options[n]; n++) { diff --git a/test/test_e2e.py b/test/test_e2e.py index b6ac1dc..9b1bc4a 100644 --- a/test/test_e2e.py +++ b/test/test_e2e.py @@ -530,6 +530,31 @@ def test_inline_option_ng(mscp, src_prefix, dst_prefix, option): src.cleanup() +@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix) +def test_porxyjump_ok(mscp, src_prefix, dst_prefix): + """ test -J proxyjump option""" + src = File("src", size = 10 * 1024 * 1024).make() + dst = File("dst") + # use small min-chunk-size to use multiple connections + run2ok([mscp, "-n", 4, "-s", 1024 * 1024, "-vvv", + "-J", "localhost:8022", + src_prefix + src.path, dst_prefix + dst.path]) + assert check_same_md5sum(src, dst) + src.cleanup() + dst.cleanup() + + +@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix) +def test_porxyjump_ng(mscp, src_prefix, dst_prefix): + """ test -J proxyjump option, invalid jump node causes fail""" + src = File("src", size = 10 * 1024 * 1024).make() + dst = File("dst") + # use small min-chunk-size to use multiple connections + run2ng([mscp, "-n", 4, "-s", 1024 * 1024, "-vvv", + "-J", "invaliduser@localhost:8022", + src_prefix + src.path, dst_prefix + dst.path]) + src.cleanup() + # username test assumes that this test runs inside a container, see Dockerfiles def test_specify_passphrase_via_env(mscp): src = File(os.getcwd() + "/src", size = 1024).make()