From c65d68f9f76efc9a8fb15a13c742188bcecdd990 Mon Sep 17 00:00:00 2001 From: drieseng Date: Sat, 19 Mar 2022 13:29:29 +0100 Subject: [PATCH 01/10] Support keyboard-interactive authentication: * Use PAM-enabled version of OpenSSH. * Install shadow package to allow us to use the chage command to force password expiry. --- docker/DockerFile | 6 +++++- docker/server/script/start.sh | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docker/DockerFile b/docker/DockerFile index 778f294..9c8f0cd 100644 --- a/docker/DockerFile +++ b/docker/DockerFile @@ -7,6 +7,8 @@ COPY --chown=sshnet:sshnet user/sshnet /home/sshnet/.ssh RUN apk add --no-cache syslog-ng && \ # install and configure sshd apk add --no-cache openssh && \ + # install openssh-server-pam to allow for keyboard-interactive authentication + apk add --no-cache openssh-server-pam && \ chmod 400 /etc/ssh/ssh*key && \ sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \ sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \ @@ -29,7 +31,9 @@ RUN apk add --no-cache syslog-ng && \ passwd -u sshnetadm && \ echo 'sshnetadm:ssh4ever' | chpasswd && \ addgroup sshnetadm sudo && \ - dos2unix /opt/sshnet/* + dos2unix /opt/sshnet/* && \ + # install shadow package; we use chage command in this package to expire/unexpire password of the sshnet user + apk add --no-cache shadow EXPOSE 22 22 diff --git a/docker/server/script/start.sh b/docker/server/script/start.sh index baa6464..c4be84f 100644 --- a/docker/server/script/start.sh +++ b/docker/server/script/start.sh @@ -1,4 +1,5 @@ #!/bin/ash /usr/sbin/syslog-ng -/usr/sbin/sshd +# start PAM-enabled ssh daemon as we also want keyboard-interactive authentication to work +/usr/sbin/sshd.pam tail -f < /var/log/auth.log \ No newline at end of file From c770b34a717ded2cbc5dd1f0c4c40d71b6bb73fa Mon Sep 17 00:00:00 2001 From: drieseng Date: Sat, 19 Mar 2022 13:31:14 +0100 Subject: [PATCH 02/10] Support keyboard-interactive authentication: * Update Restart() to stop and start the PAM-enabled version of OpenSSH. * Add method to configure KbdInteractiveAuthentication option. --- src/SshNetTests/RemoteSshd.cs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/SshNetTests/RemoteSshd.cs b/src/SshNetTests/RemoteSshd.cs index 1b5904a..7652f65 100644 --- a/src/SshNetTests/RemoteSshd.cs +++ b/src/SshNetTests/RemoteSshd.cs @@ -29,14 +29,14 @@ public RemoteSshd Restart() client.Connect(); // Kill all processes that start with 'sshd' and that run as root - var stopCommand = client.CreateCommand("sudo pkill -9 -U 0 sshd"); + var stopCommand = client.CreateCommand("sudo pkill -9 -U 0 sshd.pam"); var stopOutput = stopCommand.Execute(); if (stopCommand.ExitStatus != 0) { throw new ApplicationException($"Stopping ssh service failed with exit code {stopCommand.ExitStatus}.\r\n{stopOutput}"); } - var resetFailedCommand = client.CreateCommand("sudo /usr/sbin/sshd"); + var resetFailedCommand = client.CreateCommand("sudo /usr/sbin/sshd.pam"); var resetFailedOutput = resetFailedCommand.Execute(); if (resetFailedCommand.ExitStatus != 0) { @@ -76,12 +76,32 @@ public RemoteSshdConfig(RemoteSshd remoteSshd, IConnectionInfoFactory connection } } - public RemoteSshdConfig WithChallengeResponseAuthentication(bool value) + /// + /// Specifies whether challenge-response authentication is allowed. + /// + /// to allow challenge-response authentication. + /// + /// The current instance. + /// + public RemoteSshdConfig WithChallengeResponseAuthentication(bool? value) { _config.ChallengeResponseAuthentication = value; return this; } + /// + /// Specifies whether to allow keyboard-interactive authentication. + /// + /// to allow keyboard-interactive authentication. + /// + /// The current instance. + /// + public RemoteSshdConfig WithKeyboardInteractiveAuthentication(bool value) + { + _config.KeyboardInteractiveAuthentication = value; + return this; + } + public RemoteSshdConfig WithAuthenticationMethods(string user, string authenticationMethods) { var sshNetMatch = _config.Matches.FirstOrDefault(m => m.Users.Contains(user)); @@ -164,7 +184,7 @@ public RemoteSshdConfig WithLogLevel(LogLevel logLevel) public RemoteSshdConfig WithUsePAM(bool usePAM) { - _config.UsePAM = true; + _config.UsePAM = usePAM; return this; } From 29cf727b2dec421348a6134f6a530e7492d31a4c Mon Sep 17 00:00:00 2001 From: drieseng Date: Sat, 19 Mar 2022 13:31:38 +0100 Subject: [PATCH 03/10] Fix path to sftp module. --- src/SshNetTests/Common/RemoteSshdConfigExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SshNetTests/Common/RemoteSshdConfigExtensions.cs b/src/SshNetTests/Common/RemoteSshdConfigExtensions.cs index c058d8b..9c66efb 100644 --- a/src/SshNetTests/Common/RemoteSshdConfigExtensions.cs +++ b/src/SshNetTests/Common/RemoteSshdConfigExtensions.cs @@ -10,11 +10,12 @@ public static void Reset(this RemoteSshdConfig remoteSshdConfig) { remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, DefaultAuthenticationMethods) .WithChallengeResponseAuthentication(false) + .WithKeyboardInteractiveAuthentication(false) .WithLogLevel(LogLevel.Debug3) .ClearHostKeyFiles() .AddHostKeyFile(HostKeyFile.Rsa.FilePath) .ClearSubsystems() - .AddSubsystem(new Subsystem("sftp", "/usr/lib/openssh/sftp-server")) + .AddSubsystem(new Subsystem("sftp", "/usr/lib/ssh/sftp-server")) .ClearCiphers() .ClearKeyExchangeAlgorithms() .ClearHostKeyAlgorithms() From c02cd7024a17c736fd7f96f4d508412e1071fc64 Mon Sep 17 00:00:00 2001 From: drieseng Date: Sat, 19 Mar 2022 13:33:43 +0100 Subject: [PATCH 04/10] Update TearDown to: * Reset password of sshnet user back to the default password. * Use change to remove password expiration for the sshnet user. Update keyboard-interactive tests to also enable KbdInteractiveAuthentication option. --- src/SshNetTests/AuthenticationTests.cs | 37 ++++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/SshNetTests/AuthenticationTests.cs b/src/SshNetTests/AuthenticationTests.cs index 4978e32..34fcb91 100644 --- a/src/SshNetTests/AuthenticationTests.cs +++ b/src/SshNetTests/AuthenticationTests.cs @@ -26,9 +26,23 @@ public void SetUp() [TestCleanup] public void TearDown() { - if (_remoteSshdConfig != null) + _remoteSshdConfig?.Reset(); + + using (var client = new SshClient(_adminConnectionInfoFactory.Create())) { - _remoteSshdConfig.Reset(); + client.Connect(); + + // Reset the password back to the "regular" password. + using (var cmd = client.RunCommand($"echo \"{Users.Regular.Password}\n{Users.Regular.Password}\" | sudo passwd " + Users.Regular.UserName)) + { + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } + + // Remove password expiration + using (var cmd = client.RunCommand($"sudo chage --expiredate -1 " + Users.Regular.UserName)) + { + Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); + } } } @@ -37,6 +51,7 @@ public void Multifactor_KeyboardInteractiveAndPublicKey() { _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive,publickey") .WithChallengeResponseAuthentication(true) + .WithKeyboardInteractiveAuthentication(true) .Update() .Restart(); @@ -95,7 +110,6 @@ public void Multifactor_Password_MatchPartialSuccessLimit() public void Multifactor_Password_Or_PublicKeyAndKeyboardInteractive() { _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,keyboard-interactive") - .WithChallengeResponseAuthentication(false) .Update() .Restart(); @@ -111,7 +125,6 @@ public void Multifactor_Password_Or_PublicKeyAndKeyboardInteractive() public void Multifactor_Password_Or_PublicKeyAndPassword_BadPassword() { _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,password") - .WithChallengeResponseAuthentication(false) .Update() .Restart(); @@ -136,7 +149,6 @@ public void Multifactor_Password_Or_PublicKeyAndPassword_BadPassword() public void Multifactor_PasswordAndPublicKey_Or_PasswordAndPassword() { _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,publickey password,password") - .WithChallengeResponseAuthentication(false) .Update() .Restart(); @@ -169,7 +181,6 @@ public void Multifactor_PasswordAndPublicKey_Or_PasswordAndPassword() public void Multifactor_PasswordAndPassword_Or_PublicKey() { _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password publickey") - .WithChallengeResponseAuthentication(false) .Update() .Restart(); @@ -192,7 +203,6 @@ public void Multifactor_PasswordAndPassword_Or_PublicKey() public void Multifactor_Password_Or_Password() { _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password password") - .WithChallengeResponseAuthentication(false) .Update() .Restart(); @@ -219,7 +229,7 @@ public void KeyboardInteractive_PasswordExpired() { client.Connect(); - // Temporarity modify password so that when a expire this password, we change reset the password back to + // Temporarity modify password so that when we expire this password, we change reset the password back to // the "regular" password. using (var cmd = client.RunCommand($"echo \"{temporaryPassword}\n{temporaryPassword}\" | sudo passwd " + Users.Regular.UserName)) { @@ -227,7 +237,7 @@ public void KeyboardInteractive_PasswordExpired() } // Force the password to expire immediately - using (var cmd = client.RunCommand($"sudo passwd --expire " + Users.Regular.UserName)) + using (var cmd = client.RunCommand($"sudo chage -d 0 " + Users.Regular.UserName)) { Assert.AreEqual(0, cmd.ExitStatus, cmd.Error); } @@ -235,6 +245,7 @@ public void KeyboardInteractive_PasswordExpired() _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive") .WithChallengeResponseAuthentication(true) + .WithKeyboardInteractiveAuthentication(true) .Update() .Restart(); @@ -274,14 +285,6 @@ public void KeyboardInteractive_PasswordExpired() client.Connect(); Assert.AreEqual(4, authenticationPromptCount); } - - _remoteSshdConfig.Reset(); - - connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod()); - using (var client = new SftpClient(connectionInfo)) - { - client.Connect(); - } } } } From 1a2518e68ae53d78d9947dfd7ab25f8f3396e34d Mon Sep 17 00:00:00 2001 From: drieseng Date: Sat, 19 Mar 2022 13:34:30 +0100 Subject: [PATCH 05/10] Remove dangling images after building the image. --- docker/build.cmd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/build.cmd b/docker/build.cmd index b0b5836..9011e63 100644 --- a/docker/build.cmd +++ b/docker/build.cmd @@ -1,2 +1,7 @@ @echo off -docker build -t sshnet -f DockerFile . \ No newline at end of file + +rem Build new image +docker build -t sshnet -f DockerFile . + +rem Remove danging images +docker image prune -f \ No newline at end of file From d9253d98fbb0e45eeda17722bc13ce51a9789cd0 Mon Sep 17 00:00:00 2001 From: drieseng Date: Sat, 19 Mar 2022 13:34:47 +0100 Subject: [PATCH 06/10] Fix warning. --- src/SshNetTests/SshTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SshNetTests/SshTests.cs b/src/SshNetTests/SshTests.cs index d9b7974..c3af625 100644 --- a/src/SshNetTests/SshTests.cs +++ b/src/SshNetTests/SshTests.cs @@ -63,7 +63,7 @@ public void Ssh_ShellStream_Exit() shellStream.ReadLine(); Assert.Fail(); } - catch (NullReferenceException ex) + catch (NullReferenceException) { } From dcce660d2c2d40c86496bbcf715166247c94ad17 Mon Sep 17 00:00:00 2001 From: drieseng Date: Mon, 21 Mar 2022 21:37:16 +0100 Subject: [PATCH 07/10] Enable PAM to allow keyboard-interactive authentication to work with a vanilla container. --- src/SshNetTests/AuthenticationTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SshNetTests/AuthenticationTests.cs b/src/SshNetTests/AuthenticationTests.cs index 34fcb91..5dae02b 100644 --- a/src/SshNetTests/AuthenticationTests.cs +++ b/src/SshNetTests/AuthenticationTests.cs @@ -52,6 +52,7 @@ public void Multifactor_KeyboardInteractiveAndPublicKey() _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive,publickey") .WithChallengeResponseAuthentication(true) .WithKeyboardInteractiveAuthentication(true) + .WithUsePAM(true) .Update() .Restart(); @@ -110,6 +111,9 @@ public void Multifactor_Password_MatchPartialSuccessLimit() public void Multifactor_Password_Or_PublicKeyAndKeyboardInteractive() { _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,keyboard-interactive") + .WithChallengeResponseAuthentication(true) + .WithKeyboardInteractiveAuthentication(true) + .WithUsePAM(true) .Update() .Restart(); @@ -246,6 +250,7 @@ public void KeyboardInteractive_PasswordExpired() _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive") .WithChallengeResponseAuthentication(true) .WithKeyboardInteractiveAuthentication(true) + .WithUsePAM(true) .Update() .Restart(); From c323060d44b2efb3816645fa718f584f146ed63c Mon Sep 17 00:00:00 2001 From: drieseng Date: Thu, 24 Mar 2022 07:45:40 +0100 Subject: [PATCH 08/10] Move pruning into separate batch file. --- docker/build.cmd | 5 +---- docker/prune.cmd | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 docker/prune.cmd diff --git a/docker/build.cmd b/docker/build.cmd index 9011e63..3d14783 100644 --- a/docker/build.cmd +++ b/docker/build.cmd @@ -1,7 +1,4 @@ @echo off rem Build new image -docker build -t sshnet -f DockerFile . - -rem Remove danging images -docker image prune -f \ No newline at end of file +docker build -t sshnet -f DockerFile . \ No newline at end of file diff --git a/docker/prune.cmd b/docker/prune.cmd new file mode 100644 index 0000000..50f1629 --- /dev/null +++ b/docker/prune.cmd @@ -0,0 +1,4 @@ +@echo off + +rem Remove dangling images +docker image prune -f \ No newline at end of file From a83eb341475bac2aceee2313e7d80fc707aff669 Mon Sep 17 00:00:00 2001 From: drieseng Date: Thu, 24 Mar 2022 07:47:32 +0100 Subject: [PATCH 09/10] Add newline at end of file. --- docker/build.cmd | 2 +- docker/prune.cmd | 2 +- docker/run.cmd | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/build.cmd b/docker/build.cmd index 3d14783..9b823df 100644 --- a/docker/build.cmd +++ b/docker/build.cmd @@ -1,4 +1,4 @@ @echo off rem Build new image -docker build -t sshnet -f DockerFile . \ No newline at end of file +docker build -t sshnet -f DockerFile . diff --git a/docker/prune.cmd b/docker/prune.cmd index 50f1629..262d66c 100644 --- a/docker/prune.cmd +++ b/docker/prune.cmd @@ -1,4 +1,4 @@ @echo off rem Remove dangling images -docker image prune -f \ No newline at end of file +docker image prune -f diff --git a/docker/run.cmd b/docker/run.cmd index 398c267..299e7a4 100644 --- a/docker/run.cmd +++ b/docker/run.cmd @@ -1,2 +1,2 @@ @echo off -docker run --rm -p 22:22 --name sshnet -d sshnet \ No newline at end of file +docker run --rm -p 22:22 --name sshnet -d sshnet From 90bb1a1bee7f045d25008fd92a5f6cd56ad74a19 Mon Sep 17 00:00:00 2001 From: drieseng Date: Thu, 24 Mar 2022 07:47:32 +0100 Subject: [PATCH 10/10] Add newline at end of file. --- docker/build.cmd | 2 +- docker/prune.cmd | 2 +- docker/run.cmd | 2 +- docker/server/script/start.sh | 7 ++++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docker/build.cmd b/docker/build.cmd index 3d14783..9b823df 100644 --- a/docker/build.cmd +++ b/docker/build.cmd @@ -1,4 +1,4 @@ @echo off rem Build new image -docker build -t sshnet -f DockerFile . \ No newline at end of file +docker build -t sshnet -f DockerFile . diff --git a/docker/prune.cmd b/docker/prune.cmd index 50f1629..262d66c 100644 --- a/docker/prune.cmd +++ b/docker/prune.cmd @@ -1,4 +1,4 @@ @echo off rem Remove dangling images -docker image prune -f \ No newline at end of file +docker image prune -f diff --git a/docker/run.cmd b/docker/run.cmd index 398c267..299e7a4 100644 --- a/docker/run.cmd +++ b/docker/run.cmd @@ -1,2 +1,2 @@ @echo off -docker run --rm -p 22:22 --name sshnet -d sshnet \ No newline at end of file +docker run --rm -p 22:22 --name sshnet -d sshnet diff --git a/docker/server/script/start.sh b/docker/server/script/start.sh index c4be84f..e7e9f75 100644 --- a/docker/server/script/start.sh +++ b/docker/server/script/start.sh @@ -1,5 +1,10 @@ #!/bin/ash /usr/sbin/syslog-ng + +# allow us to make changes to /etc/hosts; we need this for the port forwarding tests +chmod 777 /etc/hosts + # start PAM-enabled ssh daemon as we also want keyboard-interactive authentication to work /usr/sbin/sshd.pam -tail -f < /var/log/auth.log \ No newline at end of file + +tail -f < /var/log/auth.log